NSTimer内存泄漏问题排查和解除

387 阅读2分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第21天,点击查看活动详情

前言

在iOS开发中,我们使用定时器(timer)的几率很高,系统中最常用的方式有GCD中提供的timer接口和我们今天要讨论的NSTimer。关于GCD相关的接口,我们今天不讨论,我们接下来看看NSTimer.

对于NSTimer,我们都知道这家伙最让人深恶痛绝的,肯定就是它容易引起循环引用,造成内存泄漏,甚至在执行定时任务的时候导致crash。

我们接下将会分析NSTimer是如何引起循环引用,并给出几种方案来破开这种循环引用状态。

NSTimer造成内存泄漏的原因

我们查看scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:timerWithTimeInterval:target:selector:userInfo:repeats:的接口文档,可以看到对target的有这样的一句解释:

The timer maintains a strong reference to target until it (the timer) is invalidated.

哦豁!NSTimer会对target进行强持有,如果这个时候作为target的对象(例如我们最常见的情况:UIViewController)强持有NSTimer,这就引起了循环引用。

既然引起了循环引用,那么在破开这个引用环之前,NSTimertarget都无法被释放,这就很合理了。

既然这样,我们就破开引用环。我们在必要的时候在target中将NSTimer置为nil。这样是否就可以释放NSTimer了呢?答案是:不行。有兴趣的同学可以试试,在NSTimerselector中加一条打印,我们会发现这该死的打印根本不会自己停,直到你因为再也不想看到它而关掉调试为止。

在我们心态爆炸,一脸懵逼的时候,我们再看看官方文档对NSTimer的一段介绍:

Timers work in conjunction with run loops. Run loops maintain strong references to their timers, so you don’t have to maintain your own strong reference to a timer after you have added it to a run loop.

这段话提到了一个关键点:在我们将NSTimer加入到RunLoop中之后,RunLoop就会维持对NSTimer的强引用。这也是不管我们是否在控制器中对NSTimer进行强引用,在我们没有将NSTimer进行invalidate之前,NSTimer都无法被释放的原因了。

说到invalidate,我们看看官方文档对这个方法的介绍:

Stops the timer from ever firing again and requests its removal from its run loop.

我们可以看到invalidate方法做了两个事情:1.停止了NSTimer的定时事件。2.将NSTimerRunLoop中移除。

看到这里是否是恍然大悟,原来NSTimer得已被释放,最大的功臣是invalidate方法,如果没有它,我们想从RunLoop中移除NSTimer可就是个麻烦的事了。

所以如果要释放NSTimer,我么必须做到两件事:1.打破NSTimertarget的引用环。2.将NSTimerRunLoop中移除,即调用NSTimerinvalidate方法。