如何打破NSTimer与Target之间的Retain Cycle

144
作者 油炸小蚯蚓
2016.01.28 01:45 字数 614

NSTimer

NSTimerFoundation框架中一种很方便很有用的对象,可以:

  • 指定绝对的日期和时期,以便到时执行指定任务
  • 指定执行任务的相对延迟时间
  • 指定重复运行的任务

计时器要和run loop(运行循环)相关联,run loop到时候会触发任务。创建NSTimer时,可以将其预先安排在当前run loop中,也可以先创建好,然后手动调用加入run loop中,它才能正常触发任务。
系统方法:

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)timeInterval
                                     target:(id)target
                                   selector:(SEL)aSelector
                                   userInfo:(nullable id)userInfo
                                    repeats:(BOOL)repeats;

直接创建个实例对象,并将其加入当前run loop当中。由于计时器会保留目标对象target,所以反复执行任务通常会导致应用程序问题。也就是设置重复执行模式的那种计时器,很容易引入retain cycle(保留环)。

如何打破Retain Cycle

  • MSWeakTimer
  • NSTimer添加个handlerBlock

推荐MSWeakTimer

线程安全的Timer,不会对target进行retain操作,支持GCD Queue,NSTimer的替代品MSWeakTimer现已支持Pod,具体实现及用法请点击这里

下面主要谈谈第二种,如何自己实现来打破retain cycle
1.为NSTimer添加一个Category方法
NSTimer+WeakTimer.h

+ (NSTimer *)zx_scheduledTimerWithTimeInterval:(NSTimeInterval)timeInterval
                                       repeats:(BOOL)repeats
                                  handlerBlock:(void(^)())handler;

NSTimer+WeakTimer.m

+ (NSTimer *)zx_scheduledTimerWithTimeInterval:(NSTimeInterval)timeInterval
                                       repeats:(BOOL)repeats
                                  handlerBlock:(void(^)())handler
{
    return [self scheduledTimerWithTimeInterval:timeInterval
                                         target:self
                                       selector:@selector(handlerBlockInvoke:)
                                       userInfo:[handler copy]
                                        repeats:repeats];
}

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

2.如何使用这个Category方法
创建一个NSTimer

- (void)startPolling
{
    __weak typeof(self)weakSelf = self;
    self.timer = [NSTimer zx_scheduledTimerWithTimeInterval:5.0 repeats:YES handlerBlock:^void(void){
        __strong typeof(weakSelf)strongSelf = weakSelf;
        [strongSelf doPolling];
    }];
}

执行轮询任务slector

- (void)doPolling
{
    //Todo...;
}

销毁NSTimer对象

- (void)stopPolling
{
    [self.timer invalidate];
    self.timer = nil;
}

- (void)dealloc
{
    [self.timer invalidate];
}

计时器现在的targerNSTimer类对象。这段代码先是定义了个弱引用,令其指向self,然后block捕获这个引用,而不直接去捕获普通的self变量,也就是说self不会为计时器所保留。当block开始执行时,立刻生成strong强引用,以保证实例在执行期间持续存活,不被释放。

采用这种写法后,外界指向NSTimer的实例最后一个引用被释放后,则创建NSTimer的实例也随之被系统回收。


Refer: Effective Objective-C 2.0 Tips52

Dev