第52条:别忘了NSTimer会保留其目标对象

计时器会保留目标对象,等到自身“失效”时再释放此对象。调用invalidate方法可令计时器失效;执行完毕相关任务后,一次性的计时器也会失效。开发者若将计时器设置成重复执行模式,那么必须自己调用invalidate方法,才能令其停止。

由于计时器会保留其目标对象,所以反复执行任务通常会导致应用程序出问题。也就是说,设置成重复执行模式的那种计时器,很容易引入“保留环”。如果此环能在某一时刻打破,那就不会出什么问题。

然而想要打破保留环,只能改变实例变量或者令计时器失效。

[_pollTimer invalidate];
_pollTimer = nil;

要么令系统将此实例回收,只有这样才能打破保留环。

但是有些时候 你是想控制器销毁时候去销毁定时器的,你肯定会说,那我们在dealloc里执行[invalidate]不就好了。好了,如果你说这句话,说明你没有理解NSTimer的工作了。

你可以回头看看我写的

“当我们新建一个timer时候,timer都会去对target对象持有一份,以保证timer还活着的时候,控制器不会被销毁”

也就是说 只要timer活着,控制器就不会被销毁,dealloc也就不会被执行。所以在dealloc里调用indalidate方法是没有效果的。我们暂且把这种现象称为NSTimer的保留环问题。

2)那我们如何去解决这个问题呢,当然就是想要timer不要对控制器的方法强持有。

解决的方法是在NSTimer 的基础上,建一个分类(Category) 并实现一个类方法,这里我们叫做:

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

//接口部分

@interface NSTimer (CLBlockSupport)
+(NSTimer *)kscheduledTimerWithTimeInterval:(NSTimeInterval)interval block:(void(^)())block repeats:(BOOL)repeats;
@end

//实现部分

@implementation NSTimer (CLBlockSupport)
+(NSTimer *)kscheduledTimerWithTimeInterval:(NSTimeInterval)interval block:(void (^)())block repeats:(BOOL)repeats
{    
return [self scheduledTimerWithTimeInterval:interval target:self selector:@selector(clblockInvoke:)userInfo:[block copy]repeats:repeats];
}

+(void)clblockInvoke:(NSTimer*)timer
{   
    void(^block)()=timer.userInfo;   
    if (block) 
    {        
        block();    
    }
}

定义一个NSTimer的类别,在类别中定义一个类方法。类方法有一个类型为块的参数(定义的块位于栈上,为了防止块被释放,需要调用copy方法,将块移到堆上)。
使用这个类别的方式如下:
__weak ViewController *weakSelf = self;
_timer = [NSTimer kscheduledTimerWithTimeInterval:5.0 block:^{ __strong ViewController *strongSelf = weakSelf;
[strongSelf startCounting]; } repeats:YES];

使用这种方案就可以防止NSTimer对类的保留,从而打破了循环引用的产生。__strong ViewController *strongSelf = weakSelf
主要是为了防止执行块的代码时,类被释放了。
在类的dealloc方法中,记得调用[_timer invalidate]。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容