起因
事情是这个样子的,某天我做完了需求,习惯性的查看了一下是否有内存泄漏问题。
然后,居然还真让我发现了一个,而且这个内存泄漏还比较奇怪,所有引用他的实例
都释放了,只有他自己没有。
实例引用分析
既然发现了,咱也不能当做没看见,那就解决问题吧。
如果知道是谁在持有泄漏的实例,那么问题就好解决了,所以我第一时间就查看了当时的实例引用关系图
,具体操作如下:
确保已经出现内存泄漏之后,点击xcode下方调试工具栏
上的Debug Memory Graph
按钮:
然后在左侧底部的搜索框里输入内存泄漏的实例对应的类名:
筛选后的结果如下:
根据这个关系图,很容易发现,这个内存泄漏是NSTimer
导致的,可问题是,我这个类里边没有用到NSTimer啊?不仅这个类没用到,其他的相关的类也没用到啊?
我再次过了一下代码,确定没有相关的地方用到timer,那么这个timer是哪里来?
发现并解决问题
在排查这个问题的同时,我又发现了另一个问题,在同一个类里,我的一个延迟执行没生效,延迟执行的方法大致如下:
- (void)startXXXAfterDelay:(NSInteger)seconds{
[self performSelector:@selector(xxxButtonClicked:) withObject:_xxxView afterDelay: seconds];
}
延迟没生效的问题倒是好排查,既然我没有取消延迟的逻辑,那么没生效的原因大概率是线程的问题,于是我打断点调试了下,startXXXAfterDelay:这个方法确实是在子线程执行的。
这个方法的上一级调用,是SDK的回调,我默认是主线程的,所以没特别注意,这个确实是疏忽了。
修改了一下代码,这个问题完美解决~
- (void)startXXXAfterDelay:(NSInteger)seconds{
dispatch_async(dispatch_get_main_queue(), ^{
[self performSelector:@selector(xxxButtonClicked:) withObject:_xxxView afterDelay: seconds];
});
}
然后您猜怎么着,内存泄漏的问题居然解决了
?!
所以,就是这个perform导致了内存泄漏?既然知道了答案,反着推就简单了。
经过我的分析,我认为事情是这个样子的:
-
performSelectorAfterDelay
等一系列的方法,底层是用NSTimer
实现的; - 使用类似方法的时候,实际上是创建了一个
timer
,触发间隔为delay
,仅触发一次,触发selector后,再将timer给invalidate
; - timer会提交到
执行perform方法
的线程对应的RunLoop; - 子线程默认是
不会
开启RunLoop的,除非使用者手动开启; - 在子线程执行perform方法,timer会提交到子线程的RunLoop,而子线程没有开启RunLoop,所以timer的selector
永远都不会执行
,timer也不会invalidate
; -
RunLoop
持有了timer
,timer
持有了self
,所以就内存泄漏了;
结论
- 在执行
performSelectorAfterDelay
等一系列的方法的时候,请确保是在主线程
; - 如果需要在子线程执行,请记得
开启RunLoop
;