Hello, World!

内存管理(下)

字数统计: 1.8k阅读时长: 6 min
2017/09/18 Share

在开始之前先思考一个问题,就是为什么需要内存管理?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
Analyze3
解决办法:添加[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)
Analyze2
声明错误:从未使用过的变量(Value stored to ‘xxx’ during its initialization is never read)。如下图所示
Analyze1
解决办法:把未使用的变量删除

Instruments中的Leaks动态分析

Xcode自带Instruments工具集可以很方便的检测内存泄露,为了测试效果,在一个测试使用的ViewController中填写如下代码,该代码中first和second互相强引用了对方,构成了循环引用造成内存泄露。

1
2
3
4
5
6
7
8
- (void)viewDidLoad {
[super viewDidLoad];

NSMutableArray *first = [NSMutableArray array];
NSMutableArray *second = [NSMutableArray array];
[first addObject:second];
[second addObject:first];
}

在Xcode的菜单栏选择Product -> Profile进入Instruments工具集,然后选择leaks,再单击右下角的Choose按钮开始检测。
instruments工具集
进入检测页面,如下图所示
leaks1
选择好了检测设备和检测项目,点击开始检测按钮,这时候iOS应用会运行起来,由于Leaks是动态监测,所以需要一边操作APP,一边观察Leaks的变化,稍等几秒钟,就可以看到Instruments检测到了我们这次内存泄露,点击暂停检测(也可继续检测),Instruments会用一个红色的叉叉来表示一次内存泄露的产生,如下图所示。
leaks2
暂停检测之后,点击红色叉(内存泄漏点),Leaks->选择Call Tree,如下图所示。
leaks3
点击页面底部栏的Call Tree(此处指的是Xcode8以上, Xcode7在右下角),选择Invert Call Tree和Hide System Libraries,如下图所示。
leaks4
此时界面显示是就是内存泄漏的代码部分,双击代码行,或者右键选择reveal in Xcode即可定位到内存泄漏的代码行。
leaks5
我们也可以切换到Leaks这栏,单击Cycles&Roots,就可以以图的形式显示出来循环引用。这样就可以很方便的找到循环引用对象了。
leaks6
定位到具体错误代码处,剩下的工作就需要开发者自己去完成了。

第三方工具

MLeaksFinder

点击查看具体原理,这里我只讨论它的优点:

  • 使用简单,不侵入业务逻辑代码,不用打开 Instruments
  • 不需要额外的操作,你只需开发你的业务逻辑,在你运行调试时就能帮你检测
  • 内存泄露发现及时,更改完代码后一运行即能发现(这点很重要,你马上就能意识到哪里写错了)
  • 精准,能准确地告诉你哪个对象没被释放
FBRetainCycleDetector

能够检测指定对象的引用情况,并把所存在的引用循环中各对象和引用在终端进行打印。
  Facebook开源的循环引用检测工具 FBRetainCycleDetector。当传入内存中的任意一个OC对象,FBRetainCycleDetector会递归遍历该对象的所有强引用的对象,以检测以该对象为根结点的强引用树有没有循环引用。我们知道,很多循环引用是 block 的使用不当造成的。而 FBRetainCycleDetector 最大的技术亮点,正在于如何找出一个 block 的所有强引用对象。
  然而,FBRetainCycleDetector 的使用存在两个问题:1,需要找到候选的检测对象。2,检测循环引用比较耗时。
  正是由于这两个问题,FBRetainCycleDetector 通常是结合其它工具一起使用,通过其它工具先找出候选的检测对象,然后进行有选择的检测。当 MLeaksFinder 与 FBRetainCycleDetector 结合使用时,正好能达到很好的效果。我们先通过 MLeaksFinder 找到内存泄漏的对象,然后再过FBRetainCycleDetector检测该对象有没有循环引用即可。

内存警告

这篇文章对内存警告介绍比较详细,可以仔细阅读。

CATALOG
  1. 1. 内存泄露
    1. 1.1. 什么是内存泄露
    2. 1.2. 哪些情况下会发生内存泄露
    3. 1.3. 如何检测内存泄露
      1. 1.3.1. Xcode集成功能
        1. 1.3.1.1. Analyze静态分析
        2. 1.3.1.2. Instruments中的Leaks动态分析
      2. 1.3.2. 第三方工具
        1. 1.3.2.1. MLeaksFinder
        2. 1.3.2.2. FBRetainCycleDetector
  2. 2. 内存警告