美文网首页
第52条:别忘了NSTimer会保留其目标对象

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

作者: MrSYLong | 来源:发表于2018-10-18 22:26 被阅读7次

    NSTimer(计时器)是一种很方便很有用的对象,可以指定绝对的日期与时间,以便到时执行任务,也可以指定执行执行任务的相对延迟时间,还可以重复运行任务,设定“间隔值”用来指定任务的触发频率。

    计时器要和运行循环相关联,运行循环到时候会触发任务。只有把计时器放到运行循环里,它才能正常触发任务。例如,下面这个方法可以创建计时器,并将其预先安排在当前运行循环中:

    + (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)yesOrNo;
    

    此方法创建出来的计时器,会在指定的间隔时间之后执行任务。也可以令其反复执行任务,直到开发者稍后将其手动关闭为止。target和selector表示在哪个对象上调用哪个方法。执行完任务后,一次性计时器会失效,若repeats为YES,那么必须调用invalidate方法才能使其停止。

    重复执行模式的计时器,很容易引入保留环:

    @interface EOCClass : NSObject
    - (void)startPolling;
    - (void)stopPolling;
    @end
    
    @implementation EOCClass{
        NSTimer *_poliTimer;
    }
    
    - (id) init{
        return [super init];
    }
    
    - (void)dealloc{
        [_poliTimer invalidate];
    }
    
    - (void)stopPolling{
        [_poliTimer invalidate];
        _poliTimer = nil;
    }
    
    - (void)startPolling{
        _poliTimer = [NSTimer scheduledTimerWithTimeInterval:5.0 target:self selector:@selector(p_doPoll) userInfo:nil repeats:YES];
    }
    
    - (void)p_doPoll{
        // code
    }
    

    如果创建了本类实例,并调用了startPolling方法。创建计时器的时候,由于目标对象是self,所以要保留此实例。然而,因为计时器是用实例变量存放的,所以实例也保留了计数器,于是就产生了保留环。

    调用stopPolling方法或令系统将实例回收(会自动调用dealloc方法)可以使计时器失效,从而打破循环,但无法确保startPolling方法一定调用,而由于计时器保存着实例,实例永远不会被系统回收。当EOCClass实例的最后一个外部引用移走之后,实例仍然存活,而计时器对象也就不可能被系统回收,除了计时器外没有别的引用再指向这个实例,实例就永远丢失了,造成内存泄漏。

    解决方案是采用块为计时器添加新功能:

    @interface NSTimer (EOCBlocksSupport)
    + (NSTimer*)eoc_scheduledTimerWithTimeInterval:(NSTimeInterval)interval block:(void(^)())block repeats:(BOOL)repeats;
    @end
    
    @implementation NSTimer( EOCBlocksSupport)
    
    + (NSTimer*)eoc_scheduledTimerWithTimeInterval:(NSTimeInterval)interval block:(void (^)())block repeats:(BOOL)repeats{
        return [self scheduledTimerWithTimeInterval:interval target:self selector:@selector(eoc_blockInvoke:) userInfo:[block copy] repeats:repeats];
    }
    
    + (void)eoc_blockInvoke:(NSTimer*)timer{
        void (^block)() = timer.userInfo;
        if (block) {
            block();
        }
    }
    

    再修改stopPolling方法:

    - (void)startPolling{
        __weak EOCClass *weakSelf = self;
        _poliTimer = [NSTimer eoc_scheduledTimerWithTimeInterval:5.0 block:^{
            EOCClass *strongSelf = weakSelf;
            [strongSelf p_doPoll];
        } repeats:YES];
    }
    

    这段代码先定义了一个弱引用指向self,然后用块捕获这个引用,这样self就不会被计时器所保留,当块开始执行时,立刻生成strong引用,保证实例在执行器继续存活。

    相关文章

      网友评论

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

          本文链接:https://www.haomeiwen.com/subject/wdkezftx.html