内存泄露问题是每个app都必须关注的问题,关系到线上的稳定性和性能。内存泄露会导致:
由于内存资源不足发生难以排查的线上bug
由于被泄露对象的存在导致的一些业务bug,比如泄露的对象仍在接受全局通知
性能问题,导致用户使用app时越来越卡
内存泄露发现分成静态扫描和运行时检测,本文主要讲运行时检测。
静态扫描
静态扫描的工具:
xcode analyzer
OCLint
Infer
运行时动态检测
由于静态扫描不能发现所有的内存泄露问题,所以运动时检测是必不可少的。
Allocation
Xcode的Allocation可以通过比较不同时间段的对象,分析出是否发生了内存泄露。但是前提是每个页面的命名要符合规范,比如宝贝详情页的所有对象都是应用前缀+宝贝详情页面前缀+具体对象名,这样才退出页面的时候才能知道相应的对象是否被释放了,因为stack最底部的页面的对象常驻在内存中。
Allocation通过和Monkey配合,在回归测试的时候,自动运行并通过计算内存占用率可以判断出新的开发版本是否发生了内存泄露,但是无法知道具体是哪个对象没有释放,只能是全局的判断。
MLLeaksFinder
https://github.com/Zepo/MLeaksFinder
非常实用的内存泄露检查工具,主要原理是UINavigationController在pop页面的时候,可以预测出被pop的页面将被释放,所以在一段时间后(3s)对这个对象进行断言判断,如果没有被释放的话程序就会中断,还可以扫描view hierarchy,或者自定义扫描需要的对象。
相比Allocation,最大的好处就是
及时发现正在开发的页面是否发生了内存泄露,及时排查,而不是在回归测试的时候才发现
如果应用中的每个页面都有相应配置的URL scheme的话,那么把所有页面的url写在配置文件中,在回归测试的时候,代码里自动push每个url的页面,可以做到完整地回归所有页面,又免去了Allocation手动点击各个页面的繁琐。
如果嫌MLLeaksFinder代码太多,可以自己实现一个精简版的,原理比较简单,代码写起来很少。
如何排查
上述的各种方法发现了内存泄露后,排查并解决才是最关键的一步。根据泄露的对象,重点排查block和两个以上对象循环引用的情况。
如果是ViewController发生内存泄露,重点查看ViewController里面的block是否忘记声明weak了,因为ViewController被其他对象持有的情况不常见。如果是用RAC的话,记得weakly和strongly要成对出现,否则会发生内存泄露。
最常发生的是View的内存泄露问题,首先查看是发生内存泄露的view之间是否有delegate的关系,如果有的话看看delegate属性有没有声明weak
由于service或者读取缓存等异步操作引起的内存泄露问题
总结
内存泄露问题主要还是靠编码时的规范,静态扫描能解决一部分问题,动态扫描是最后的保证。而排查内存泄露经验很重要。