iOS开发之核心优化——内存管理
1. 内存消耗
iPhone 和 iPad 设备的内存资源非常有限。如果某个应用的内存使用量超过了单个进程的上限,那么它就会被操作系统终止使用。正是由于这个原因,成功的内存管理在 iOS 应用的实现过程中扮演着核心的角色。应用中的内存消耗分为两部分:栈大小和堆大小。
1.1 栈大小
应用中新创建的每个线程都有专用的栈空间,该空间由保留的内存和初始提交的内存组成。栈可以在线程存在期间自由使用。线程的最大栈空间很小,这就决定了以下的限制
- 可被递归调用的最大方法数:每个方法都有其自己的栈帧,并会消耗整体的栈空间。
例如:如果你调用方法 main,那么 main 将调用 method1,而 method1 又将调用 method2,这就存在三个栈帧了,且每个栈帧都会消耗一定字节的内存 - 一个方法中最多可以使用的变量个数:所有的变量都会载入方法的栈帧中,并消耗一定的栈空间。
- 视图层级中可以嵌入的最大视图深度:渲染复合视图将在整个视图层级树中递归地调用 layoutSubViews 和 drawRect 方法。如果层级过深,可能会导致栈溢出。
1.2 堆大小
每个进程的所有线程共享同一个堆。一个应用可以使用的堆大小通常远远小于设备的 RAM 值。保持应用的内存需求总是处于 RAM 的较低占比是一个非常好的主意。虽然没有强制规定,但强烈建议使用量不要超过 80%~85%,要给操作系统的核心服务留下足够多的内存。不要忽视 didReceiveMemoryWarning 信号。
2. 内存管理模型
内存管理模型基于持有关系的概念。
如果一个对象正处于被持有状态,那它占用的内存就不能被回收。
- 当一个对象创建于某个方法的内部时,那该方法就持有这个对象了。
- 如果这个对象从方法返回,则调用者声称建立了持有关系。这个值可以赋值给其他变量,对应的变量同样会声称建立了持有关系。
- 一旦与某个对象相关的任务全部完成,那么就是放弃了持有关系。
- 这一过程没有转移持有关系,而是分别增加或减少了持有者的数量。当持有者的数量降为零时,对象会被释放,相关的内存会被回收。这种持有关系计数通常被正式称为引用计数。当你亲自管理时,它被称为手动引用计数(manual reference counting,MRC)。虽然现在已经十分罕见,但 MRC 对理解问题很有帮助。现如今的应用大都使用自动引用计数(automatic reference counting ,ARC)
内存管理规则
- 你拥有所有自己创建的对象,如 new、alloc、copy 或 mutableCopy。
- 你可以用 MRC 中的 retain 或者 ARC 中的 __strong 引用来拥有任何对象的持有关系。
- 在 MRC 中,当不再需要某个对象时,你必须立即使用 release 方法来放弃对该对象的持有关系。而在 ARC 中则无需任何特殊操作。持有关系会在对象失去最后的引用(如
方法中的最后一行代码)时被抛弃。 - 一定不能抛弃原本并不存在持有关系的对象。
避免循环引用的规则
- 对象不应该持有它的父对象,应该用 weak 引用指向它的父对象
- 作为必然的结果,一个层级体系中的子对象应该保留祖先对象。
- 连接对象不应持有它们的目标对象。目标对象的角色是持有者。连接对象包括以下几种:
(1) 使用委托的对象。委托应该被当作目标对象,即持有者。
(2) 包含目标和 action 的对象,这是由上一条规则推理得到的。例如, UIButton 会调用它的目标对象上的 action 方法。按钮不应该保留它的目标。
(3) 观察者模式中被观察的对象。观察者就是持有者,并会观察发生在被观察对象上的变化。 - 使用专用的销毁方法中断循环引用。
双向链表中存在循环引用,环形链表中也存在循环引用。
在这类情况下,一旦明确对象不会再被使用时(当链表的表头超出作用范围),你要编写代码以打破链表的链接。