NSTimer 的强引用错在哪了?

iOS Feb 13, 2020

iOS 开发新手,常常抱怨"计时器又导致内存泄露了",这时度娘很贴心的“送来”各种解决方案,解决了因循环引用导致的内存泄露问题。但作为工程师的我们,岂能就这样放过 NSTimer 呢?

首先,内存泄漏是个很严肃的问题,我相信 Apple 的工程师是不会能出这种低级错误的。

其次,作为  Apple 提出的 Target-Action 模式中,明确说明了 Target 是弱引用,类似于委托模式。

由于 ObjC 采用引用计数来管理内存,开发者对于引用关系具有敏感的洞察力是基本功。不过 Apple 官方对于 NSTimer 强引用 Target 这件事却没有明确的说明(符合苹果的一贯风格)

那么问题来了:

  1. NSTimer 对于 Target 模式是否符合苹果自身的规范?
  2. 强引用 Target 发生的时机在何时?
  3. 一旦发生,是否永久生效? 如果永久生效,意味着这就是一个框架 bug。

带着疑问,我们先从官方文档进行求证

通过代码验证

@interface NXTimerTarget : NSObject

- (void)handleTimer;

@end
@implementation NXTimerTarget

- (void)handleTimer
{
    NSLog(@"%s", __func__);
}

-(void)dealloc
{
    NSLog(@"%s", __func__);
}

@end
NXTimerTarget *target = [NXTimerTarget new];
NSTimer *nstimer =
[NSTimer timerWithTimeInterval:2.0 target:target selector:@selector(handleTimer) userInfo:nil repeats:false];
// 可以观察到,NXTimerTarget 很快就是放掉了
//当加入到 NSRunLoop 后,计时器会正常工作直到关闭后,NXTimerTarget 被释放
[[NSRunLoop currentRunLoop] addTimer:nstimer forMode:NSRunLoopCommonModes];

证实结论:

  1. NSTimer 对象实例化过程中对与 Target 并非强引用,说明 NSTimer 遵循了 Target-Action 模式
  2. 强引用的时机发生在添加到 NSRunLoop 后
  3. 在 NSTimer 结束后便会释放强引用

强引用的存在是合理的,我们只需要在使用 NSTimer 的时候,遵循合理的引用计数规则。

Apple 犯下的唯一错误是,没有对 NSTimer 何时及为何强引用 Target 进行说明,正确的指导开发人员使用 NSTimer。

Nicholas X.

山穷水复疑无路,柳暗花明又一村