简介
内存泄露在性能优化中一直是一个老生常谈的问题,而且最常出现在UIViewController的使用过程中。一般当VC退出UI堆栈后,如果使用过的内存没有被释放,就会产生内存泄露。所以在使用UIViewController的时候需要特别注意。
内存泄露原理及解决方案
一般在VC中出现内存泄露主要原理是产生了循环引用导致内存得不到释放,而循环引用产生的主要case大概了以下三种情况:
NSTimer
在使用NSTimer的时候一般会把target设置为self,而timer本身又是self的成员变量,这样就会产生循环引用的问题。
打破循环的方式是在VC退出时停掉timer并将timer成员变量置空。
delegate
delegate产生循环引用的原理和timer类同,一般我们会将delegate修饰为weak来打破循环
block
block也是最容易产生循环引用的地方,稍不注意就会造成内存泄露。所以在使用block的时候也需要格外留神。block产生循环引用的原理主要还得从它的内存说起,这里不细说(网上讲这个case的情况不要太多)。
有2中方法可以打破block的循环引用:
- 使用weak修饰self
- 使用__block修饰self,在block内部将self置空
不过方案2有个小问题,如果该block没有被调用的话这个循环引用就没法被打破了,所以通常我们都使用第一种方案。
但是在开发业务的过程中难免会有疏漏的地方,经验丰富的老鸟还好,如果组内有小萌新的话,这种问题就很可能会出现,这里写这个工具也是做一个保护。
内存泄露检测原理
当VC被pop或者dismiss后,正常的流程是走到dealloc,然后使用的内存会被释放,self会为nil,如果有循环引用,则不会走dealloc,内存得不到释放,self也不会为nil.
这里我们就利用了释放后self会被置空,而不释放self不会为nil的原理来实现内存泄露的警告。
实现
首先我们需要hook UIViewController的下面几个方法:
- viewWillAppear
|
|
- viewDidDisappear
|
|
- dismissViewControllerAnimated
|
|
同时我们还需要hook 一下UINavigationController的pop方法:
- popViewControllerAnimated
|
|
这里当然选择在分类中进行hook最方便了。我们把hook的代码放在load函数中执行,具体原因参考这里.
|
|
这里的宏表示是测试环境。
在viewWillAppear中,我们记录一下当前VC的状态。
|
|
然后在viewDidDisappear时,我们判断一下当前VC的状态,如果VC被pop或者dismiss了,我们调用一下内存泄露提醒的方法
|
|
这里我们做了一个延时调用,前面已经说过,如果VC走正常流程,内存被释放,self会被置空,所以这里如果没有内存泄露的话,weakself应该是为nil,我们使用nil对象调用这两个方法不会有提醒,而如果发生了内存泄露,weakself不会为nil,这时调用者这2个方法就会弹出内存泄露的警告了。
当然,我们还需要再dismiss和pop的时候设置当前vc的状态为已pop的状态,只有是已经被pop了,才会去尝试调用提醒事件。因为当vc的调用栈被push到下一级,也就是上面有新的vc时,viewDidDisappear也会被调用,但是此时vc的内存还会保存在内存中,只有在被pop或者dismiss后,vc的内存才有可能被释放。
|
|
后续
这个工具目前只能检测UIViewController的内存泄露问题,后续还可以再加上其他类型的检测方法。写这个工具也算是对runtime学习的一个实践。