动态内存泄露分析
时间:2017年9月29日 周五
1、内存泄露
通常说的内存泄漏是指堆内存的泄漏。
堆内存是指程序从堆中分配的、大小任意的(内存块的大小可以在程序运行期决定),使用完后必须显式释放的内存。创建对象时,用alloc、new、copy等方法从堆中分配到一块内存,使用完后,程序必须负责相应的调用free或delete释放该内存块,否则,这块内存就不能被再次使用,我们就说这块内存泄漏了。
如果程序运行时一直分配内存而不及时释放无用的内存,程序占用的内存越来越大,直到把系统分配给该APP的内存消耗殚尽,程序因无内存可用导致崩溃。
可能引起的问题:
1)内存消耗殆尽的时候,程序会因没有内存被杀死,即crash;
2)当内存快要用完的时候,会非常的卡顿;
3)如果是ViewController没有释放掉,引起的内存泄露,还会引起其他很多问题,尤其是和通知相关的。没有被释放掉的ViewController还能接收通知,还会执行相关的动作,所以会引起各种各样的异常情况的发生。
从iOS5开始apple采用ARC机制,不需要我们对内存进行管理了。但在实际开发过程中,不可避免的出现内存泄露的情况。
2、xcode自带的工具
Instrument中的Leaks、Allocations工具可以帮助我们进行内存泄露的排查,但它们存在不足。
在 ARC 时代更常见的内存泄露是循环引用导致的 Abandoned memory,Leaks 工具查不出这类内存泄露,应用有限;
Instrument 的 Allocations 检测出 Abandoned memory,但是用起来不方便,需要一个一个地重现场景,且无法及时的知道泄露,比较繁琐。总之,一句话就是,不好使。
3、开源框架
在 GitHub 上有一些内存泄露检测相关的项目,例如 HeapInspector-for-iOS 和 MSLeakHunter。
1)HeapInspector-for-iOS
HeapInspector-for-iOS 可以说是 Allocations 的改进。它通过 hook 掉 alloc,dealloc,retain,release 等方法,来记录对象的生命周期。具体的检测内存泄露的方法和原理,与 Instrument 的 Allocations 一致。然而它跟 Allocations 一样,存在的问题是,你需要一个个场景去重复的操作,还有检测不及时。
2)MSLeakHunter
MSLeakHunter 就简单得多,它只检测 UIViewController 和 UIView,通过 hook 掉 UIViewController 的 -viewDidDisappear: 方法,并认为 -viewDidDisappear: 后,UIViewController 将很快被释放,如果 UIViewController 没有被释放,则打个建议日志。这种做法其实不是很好,-viewDidDisappear: 被调用可能是因为又 push 进来一个新的 ViewController,把当前的 ViewController 挡住了,所以可能有很多错误的建议,需要结合你实际的操作去具体地分析日志。
4、动态检测工具:MLeaksFinder
1)MLeaksFinder检测内存泄露原理
MLeaksFinder用于检测viewController、view是否存在内存泄露的情况。
当一个 UIViewController 被 pop 或 dismiss 后,该 UIViewController 包括它的 view,view 的 subviews 等等将很快被释放(除非你把它设计成单例,或者持有它的强引用,但一般很少这样做)。于是,我们只需在一个 ViewController 被 pop 或 dismiss 一小段时间后,看看该 UIViewController,它的 view,view 的 subviews 等等是否还存在。
具体的方法是,为基类 NSObject 添加一个方法 -willDealloc 方法,该方法的作用是,先用一个弱指针指向 self,并在一小段时间(3秒)后,通过这个弱指针调用 -assertNotDealloc,而 -assertNotDealloc 主要作用是直接中断言。
- (BOOL)willDealloc {
__weak id weakSelf = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
__strong id strongSelf = weakSelf;
[strongSelf assertNotDealloc];
});
return YES;
}
- (void)assertNotDealloc {
if ([MLeakedObjectProxy isAnyObjectLeakedAtPtrs:[self parentPtrs]]) {
return;
}
[MLeakedObjectProxy addLeakedObject:self];
}
这样,当我们认为某个对象应该要被释放了,在释放前调用这个方法,如果3秒后它被释放成功,weakSelf 就指向 nil,不会调用到 -assertNotDealloc 方法,也就不会中断言,如果它没被释放(泄露了),-assertNotDealloc 就会被调用中断言。这样,当一个 UIViewController 被 pop 或 dismiss 时(我们认为它应该要被释放了),我们遍历该 UIViewController 上的所有 view,依次调 -willDealloc,若3秒后没被释放,就会中断言,并且调用[MLeakedObjectProxy addLeakedObject:self];该方法会获取内存泄露的信息,并弹框显示。