在开始之前先思考一个问题,就是为什么需要内存管理?iOS系统会在程序退出以后,回收其所有内存,那么开发者还有必要在程序运行过程中去管理内存吗?
答案是肯定的,1,程序运行的过程中,如果不去管理内存,可能会发生内存泄露,而内存泄露会导致程序运行出错甚至直接崩溃闪退。虽然有些情况下的内存泄露,并不会导致上述状况,2,严格的内存管理可以让应用更加合理、高效的使用有限的硬件条件,提高应用程序的性能。
内存泄露
什么是内存泄露
内存泄露指一个对象或者变量在使用完成后没有及时释放掉,一直占用着内存,直到应用停止。
哪些情况下会发生内存泄露
iOS开发,不管是Objective-C语言还是Swift语言,其内存管理的方式都是基于引用计数的。所以如果发生了内存泄露,肯定与引用计数有关。
1,引用计数的天生缺陷是循环引用,以下是项目中常见的循环引用:
- delegate:xxxx
- block:xxxx
- controller+view:xxxx
- NSTimer:xxxx
2,错误使用手动引用计数
底层Core Foundation对象,需要手动去管理,如果创建使用完成后,没有调用CFRelease方法去释放的话,也会造成内存泄露。
3,大次数循环内存暴涨
1
2
3 while (true) {
UIViewController *ctr = [[UIViewController alloc] init];
}
该循环内产生大量的临时对象,直到循环结束才释放,可能导致内存泄露,解决方法是把循环体放到自动释放池中,及时释放占用内存大的临时变量。
1
2
3
4
5
6 while (true) {
@autoreleasepool {
UIViewController *ctr = [[UIViewController alloc] init];
}
}
4,项目中的第三方导致的内存泄露。
到github上查看三方的Issues,看是否有相关的问答。或者通过网络、书籍、前辈查询相关解决方案。
5,子线程中使用延时操作或者使用定时器不当,也会造成内存泄露。具体请看并发编程进阶中说明。
如何检测内存泄露
Xcode集成功能
Analyze静态分析
在程序没运行的时候,通过工具对代码直接进行分析,根据代码的上下文的语法结构,让编译器分析内存情况, 检查是否有内存泄露。
主要分析以下四种问题:
1,逻辑错误:访问空指针或未初始化的变量等;
2,内存管理错误:如内存泄漏等;
3,声明错误:从未使用过的变量;
4,Api调用错误:未包含使用的库和框架。
缺点:静态内存分析由于是编译器根据代码进行的判断,做出的判断不一定会准确,因此如果遇到提示, 应该去结合代码上文检查一下。
在Xcode的菜单栏选择Product -> Analyze启动静态分析。
逻辑错误:The ‘viewDidLoad’ instance method in UIViewController subclass ‘ViewController’ is missing a [super viewDidLoad] call
解决办法:添加[super viewDidLoad];
内存管理错误:内存泄漏(Object leaked: object allocated and stored into ‘cfStr’ is not referenced later in this execution path and has a retain count of +1)
声明错误:从未使用过的变量(Value stored to ‘xxx’ during its initialization is never read)。如下图所示
解决办法:把未使用的变量删除
Instruments中的Leaks动态分析
Xcode自带Instruments工具集可以很方便的检测内存泄露,为了测试效果,在一个测试使用的ViewController中填写如下代码,该代码中first和second互相强引用了对方,构成了循环引用造成内存泄露。
1 | - (void)viewDidLoad { |
在Xcode的菜单栏选择Product -> Profile进入Instruments工具集,然后选择leaks,再单击右下角的Choose按钮开始检测。
进入检测页面,如下图所示
选择好了检测设备和检测项目,点击开始检测按钮,这时候iOS应用会运行起来,由于Leaks是动态监测,所以需要一边操作APP,一边观察Leaks的变化,稍等几秒钟,就可以看到Instruments检测到了我们这次内存泄露,点击暂停检测(也可继续检测),Instruments会用一个红色的叉叉来表示一次内存泄露的产生,如下图所示。
暂停检测之后,点击红色叉(内存泄漏点),Leaks->选择Call Tree,如下图所示。
点击页面底部栏的Call Tree(此处指的是Xcode8以上, Xcode7在右下角),选择Invert Call Tree和Hide System Libraries,如下图所示。
此时界面显示是就是内存泄漏的代码部分,双击代码行,或者右键选择reveal in Xcode即可定位到内存泄漏的代码行。
我们也可以切换到Leaks这栏,单击Cycles&Roots,就可以以图的形式显示出来循环引用。这样就可以很方便的找到循环引用对象了。
定位到具体错误代码处,剩下的工作就需要开发者自己去完成了。
第三方工具
MLeaksFinder
点击查看具体原理,这里我只讨论它的优点:
- 使用简单,不侵入业务逻辑代码,不用打开 Instruments
- 不需要额外的操作,你只需开发你的业务逻辑,在你运行调试时就能帮你检测
- 内存泄露发现及时,更改完代码后一运行即能发现(这点很重要,你马上就能意识到哪里写错了)
- 精准,能准确地告诉你哪个对象没被释放
FBRetainCycleDetector
能够检测指定对象的引用情况,并把所存在的引用循环中各对象和引用在终端进行打印。
Facebook开源的循环引用检测工具 FBRetainCycleDetector。当传入内存中的任意一个OC对象,FBRetainCycleDetector会递归遍历该对象的所有强引用的对象,以检测以该对象为根结点的强引用树有没有循环引用。我们知道,很多循环引用是 block 的使用不当造成的。而 FBRetainCycleDetector 最大的技术亮点,正在于如何找出一个 block 的所有强引用对象。
然而,FBRetainCycleDetector 的使用存在两个问题:1,需要找到候选的检测对象。2,检测循环引用比较耗时。
正是由于这两个问题,FBRetainCycleDetector 通常是结合其它工具一起使用,通过其它工具先找出候选的检测对象,然后进行有选择的检测。当 MLeaksFinder 与 FBRetainCycleDetector 结合使用时,正好能达到很好的效果。我们先通过 MLeaksFinder 找到内存泄漏的对象,然后再过FBRetainCycleDetector检测该对象有没有循环引用即可。
内存警告
这篇文章对内存警告介绍比较详细,可以仔细阅读。