NSTimer内存/循环引用问题整合
日常开发最常用的定时方法还是NSTimer,CADisplayLink跟GCDTimer写起来还是略麻烦。但是对于重复的任务有一个重要问题:何时停掉这个任务。而且NSTimer经常会出现循环引用的问题,因此整合记录下。
情况一:
Timer会强持有target,所以如果我们写个vc持有一个Timer,并且这个Timer初始化以vc为target,那就循环引用了

结果:

循环情况:

这种情况下即便我们pop了TestVC,testvc dealloc依然不会出现,控制台会无限制打印a
解决方法:
合适的情况下调用timer.invalidate()手动停掉timer,打破循环:


情况二:
那如果我们不持有这个timer,在某个方法里timer只是作为一个局部变量来一vc为target,这样testvc就不会抢引用timer,是否可以避免循环引用?

结果:

依然未释放。这是因为启动timer时需要把timer添加到当前runloop中,那当前runloop就会持有timer如果不调用timer的invalidate方法,这个timer就无法被runloop释放,timer所持有的target也无法被释放。(scheduledTimer方法相当于initTimer() + fire() + runloop.addTimer()三合一)

解决方法:
标记这个timer,合适的时候手动停掉
综合解决方案:在合适的时候停掉timer
思路:使用中间target:

使用一个InnerTarget,初始化Timer时,InnerTarget弱持有testvc,持有selector,弱持有testvc,timer强引用InnerTarget,在InnerTarget的selector中,使用weak target来调用selector方法,这样testvc就可以正常dealloc,当testvc释放时,InnerTarget可以感知,停止Timer即可实现自动释放timer:

再试试看:


正常释放
iOS10 新增的block初始化Timer的方法,解决了Timer强引用target的问题,但是无法解决Runloop持有Timer的问题,仍然需要手动释放timer
尝试:使用stopTarget+block来初始化Timer,使用思路类似于Target:

使用:


可以正常释放
缺点:
因为block会对内部对象进行一次引用计数+1所以如果block里出现了self,请一定要使用weak self。否则依然会出现循环引用情况:

引用图:

使用weak/unowned self可打破引用问题:

引用图:
