返回

NSTimer 的循环引用难题:如何平衡定时功能与内存管理

IOS

NSTimer 的基本原理

NSTimer 是一个定时器类,可以用来创建并管理重复的任务。NSTimer 有两个重要的属性:

  • target:要执行任务的对象。
  • selector:要执行的任务方法。

当 NSTimer 启动后,它会每隔指定的时间间隔向 target 对象发送 selector 消息,直到 NSTimer 被销毁或停止。

NSTimer 的循环引用问题

NSTimer 经常会遇到循环引用的问题。循环引用是指两个或多个对象相互引用,导致无法释放内存的情况。

在 NSTimer 的情况下,循环引用通常发生在 target 对象和 NSTimer 之间。当 NSTimer 启动时,它会把 target 对象添加到自己的 observers 数组中。这会导致 target 对象无法释放,因为 NSTimer 会一直持有对它的引用。

如何避免 NSTimer 的循环引用

避免 NSTimer 的循环引用,有几种常见的方法:

  1. 使用弱引用

弱引用可以避免循环引用。弱引用不会阻止对象被释放,但它不会阻止对象被释放。当 target 对象被释放时,NSTimer 的 observers 数组中的弱引用就会被自动清除,从而避免了循环引用。

__weak id weakTarget = self;
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:weakTarget selector:@selector(someMethod:) userInfo:nil repeats:YES];
  1. 使用观察者模式

观察者模式是一种设计模式,可以用来解耦 target 对象和 NSTimer 之间的耦合。观察者模式下,NSTimer 会向 target 对象发送通知,而不是直接调用 target 对象的方法。这样,当 target 对象被释放时,NSTimer 也不会持有对它的引用,从而避免了循环引用。

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(someMethod:) name:@"TimerNotification" object:nil];
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(sendNotification:) userInfo:nil repeats:YES];

- (void)sendNotification:(NSTimer *)timer {
  [[NSNotificationCenter defaultCenter] postNotificationName:@"TimerNotification" object:nil];
}
  1. 使用 CADisplayLink 或 GCDTimer

CADisplayLink 和 GCDTimer 是两种替代 NSTimer 的计时器类。CADisplayLink 与屏幕的刷新率同步,GCDTimer 则基于 Grand Central Dispatch(GCD)实现。这两者都比 NSTimer 更不容易出现循环引用的问题。

CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(someMethod:)];
[displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];

dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
dispatch_source_set_timer(timer, dispatch_walltime(NULL, 0), 1.0 * NSEC_PER_SEC, 0);
dispatch_source_set_event_handler(timer, ^{
  [self someMethod];
});
dispatch_resume(timer);

何时停止 NSTimer

NSTimer 经常会出现循环引用的问题,因此,在不再需要 NSTimer 时,应及时将其停止。NSTimer 可以通过以下几种方式停止:

  • 调用 NSTimer 的 invalidate 方法。
  • 将 NSTimer 从 NSRunLoop 中移除。
  • 将 target 对象从 NSTimer 的 observers 数组中移除。

总结

NSTimer 是 iOS 开发中常用的计时器工具,但它也容易出现循环引用的问题,导致内存泄漏。本文介绍了 NSTimer 的基本原理、循环引用问题以及避免循环引用