iOS小知识之NSTimer的循环引用一

语言: CN / TW / HK

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

1.NSTimer的循环引用

1.1 常见问题

日常开发中,经常会用到NSTimer定时器,一些不正确的写法,会导致NSTimer造成循环引用,如下代码所示

``` - (void)viewDidLoad { [super viewDidLoad];

self.timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(fireHome) userInfo:nil repeats:YES]; 
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];

} - (void)fireHome{ num++; NSLog(@"hello word - %d",num); } - (void)dealloc{ [self.timer invalidate]; self.timer = nil; } `` 上述案例,一定会产生循环引用 - 创建NSTimer时,将self传入target,导致NSTimer持有self,而self又持有timer- 使用NSTimerinvalidate方法,可以解除NSTimerself的持有 - 但是案例中,NSTimerinvalidate方法,由UIViewControllerdealloc方法执行。但是selftimer持有,只要timer有效,UIViewControllerdealloc`方法就不会执行。故此双方相互等待,造成循环引用

1.2 target传入弱引用对象

NSTimertarget参数传入一个弱引用的self,能否打破对self的强持有? 代码如下:

``` - (void)viewDidLoad { [super viewDidLoad];

__weak typeof(self) weakSelf = self; 
self.timer = [NSTimer timerWithTimeInterval:1 target:weakSelf selector:@selector(fireHome) userInfo:nil repeats:YES]; 
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];

} `` - 肯定是不行的,因为在timerWithTimeInterval内部,使用强引用对象接收target`参数,所以外部定义为弱引用对象没有任何意义 这种方式类似于以下代码:

__weak typeof(self) weakSelf = self; typeof(self) strongSelf = weakSelf; 在官方文档中,对target参数进行了明确说明:

timer说明.png - target:定时器触发时指定的消息发送到的对象。计时器维护对该对象的强引用,直到它(计时器)失效


和Block的区别: Block将捕获到的弱引用对象,赋值给一个强引用的临时变量,当Block执行完毕,临时变量会自动销毁,解除对外部变量的持有。

2.常规解决方案

2.1 更换API

使用携带Block的方法创建NSTimer,避免target的强持有

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block;

2.2 在适当时机调用invalidate

根据业务需求,可以将NSTimerinvalidate方法写在viewWillDisappear方法中

``` - (void)viewWillAppear:(BOOL)animated{ [super viewWillAppear:animated];

self.timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(fireHome) userInfo:nil repeats:YES]; 
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];

} - (void)viewWillDisappear:(BOOL)animated{ [super viewWillDisappear:animated];

[self.timer invalidate];
self.timer = nil;

} `` 或者写在didMoveTo ParentViewController`方法中

- (void)viewDidLoad { [super viewDidLoad]; self.timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(fireHome) userInfo:nil repeats:YES]; [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes]; } - (void)didMoveToParentViewController:(UIViewController *)parent{ if (parent == nil) { [self.timer invalidate]; self.timer = nil; } }