内存泄漏之子线程perform...afterDelay

起因


事情是这个样子的,某天我做完了需求,习惯性的查看了一下是否有内存泄漏问题。

然后,居然还真让我发现了一个,而且这个内存泄漏还比较奇怪,所有引用他的实例都释放了,只有他自己没有。

实例引用分析


既然发现了,咱也不能当做没看见,那就解决问题吧。

如果知道是谁在持有泄漏的实例,那么问题就好解决了,所以我第一时间就查看了当时的实例引用关系图,具体操作如下:

确保已经出现内存泄漏之后,点击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导致了内存泄漏?既然知道了答案,反着推就简单了。

经过我的分析,我认为事情是这个样子的:

  1. performSelectorAfterDelay等一系列的方法,底层是用NSTimer实现的;
  2. 使用类似方法的时候,实际上是创建了一个timer,触发间隔为delay,仅触发一次,触发selector后,再将timer给invalidate;
  3. timer会提交到执行perform方法的线程对应的RunLoop;
  4. 子线程默认是不会开启RunLoop的,除非使用者手动开启;
  5. 在子线程执行perform方法,timer会提交到子线程的RunLoop,而子线程没有开启RunLoop,所以timer的selector永远都不会执行,timer也不会invalidate
  6. RunLoop持有了timertimer持有了self,所以就内存泄漏了;

结论


  1. 在执行performSelectorAfterDelay等一系列的方法的时候,请确保是在主线程
  2. 如果需要在子线程执行,请记得开启RunLoop
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容