第五章:内存管理
ARC几乎把所有的内存管理事宜都交由编译器来决定,开发者只需关注业务逻辑。
29 理解引用计数 总结:
iOS不支持垃圾回收机制,现在不支持,将来也不会支持。
30 以ARC简化引用计数 总结:
ARC通过命名约定将内存管理规则标准化。其他编程语言很少像OC这样强调命名。
ARC通过设置全局数据结构(此数据结构的具体内容因处理器而异)中的一个标志位,来代替直接调用autorelease和retain。这是ARC所带来的好处。待编译器与运行期组件日臻成熟,还会出现其他的优化技术。
CoreFoundation对象不归ARC管理,开发者必须适时调用CFRetain/CFRelease。
注意:ARC执行retain、release、autorelease这些操作并不会通过OC的消息派发机制,而是直接调用底层C语言版本。这样能节省很多的cpu周期。
31 在dealloc方法中只释放引用并解除监听 总结:
- (void)dealloc{
CFRelease(coreFoundationObject);
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
虽然说应该在dealloc中释放引用,但是开销较大或系统内稀缺的资源则不在此列。譬如文件描述符(file descriptor),套接字(socket),大块内存。不能指望dealloc必定会在某个特定时机调用,因为有一些无法预料的东西可能也持有此对象。应当当应用程序用完资源对象后,就调用清理方法。也要在- (void)applicationWillTerminate:(UIApplication *)application里面调用清理方法。
- (void)close{
_closed = YES;
}
- (void)dealloc{
if(!_closed = YES){
NSLog(@“ERROR:close was not called before dealloc”);
//有时候不想只输出错误消息,而是要抛出异常来表明不调用close方法是严重的编程错误
[self close];
}
}
虽然不要在dealloc里随便调用其他方法,但是这是为了侦测编程错误而破例。
在dealloc里也不要调用属性的存取方法,因为有人可能会覆写这些方法,并于其中做一些无法在回收阶段安全执行的操作。
在dealloc方法里,应该做的事情就是释放指向其他对象的引用,并取消原来订阅的键值观测(KVO)或NSNotificationCenter等通知。
如果对象持有文件描述符等系统资源,那么应该专门编写一个方法来释放此中资源。这样的类要和其他使用者约定:用完资源后必须调用close方法。
执行异步任务的方法不应在dealloc里调用;只能在正常状态下执行的那些方法也不应在dealloc里调用,因为此时对象已处于正在回收的状态了。
32 编写异常安全代码时留意内存管理问题 总结:
捕获异常时,一定要注意将try块内所创立的对象清理干净。
在默认情况下,ARC不生成安全处理异常所需的清理代码。开启编译器标志后,可生成这种代码,不过会导致应用程序变大,而且会降低运行效率。
例如:非arc环境下是@try{}@catch{}@finalyy{//我们可以在这里面释放对象},然而arc环境下确是@try{}@catch{}无法手动释放对象,而且此时arc并不会自动处理,因为这样做需要加入大量样板代码,以便跟踪待清理的对象。这样会严重影响性能。
有时用OC++来编码,或是编码中用到了第三方程序库而此程序库所抛出的异常又不受你控制时,就需要捕获及处理异常了。如果手工管理引用计数,而且必须捕获异常,那么要设法保证所编代码能把对象正确清理干净。若使用ARC且必须捕获异常,则需打开编译器的-fobjc-arc-exceptions标志。但最重要的是,在发现大量异常捕获操作时,应考虑重构代码。用NSError式错误信息来取代异常。
33 以弱引用避免保留循环 总结:
一般来说,如果不拥有某对象,就不要保留它。这条规则对collection例外,collection虽然并不直接拥有其内容,但是它要代表自己所属的那个对象来保留这些元素。有时,对象中的引用会指向另外一个并不归自己拥有的对象。比如delegate模式。weak引用的自动清空是随着ARC而引入的新特性,由运行期系统来实现。在具备自动清空的弱引用上,可以随意读取其数据,因为这种引用不会指向已经回收过的对象。
34 以自动释放池块降低内存峰值 总结:
虽然自动释放池块的开销不太大,但毕竟还是有的,所以尽量不要建立额外的自动释放池。NSAutoreleasePool更为重量级,不会在每次执行for循环时都清空池,通常用来创建那种偶尔需要清空的池。自动释放池块更为轻量级,可以每次执行循环时都会建立并清空自动释放池。
自动释放池排布在栈中,对象收到autorelease消息后,系统将其放入最顶端的池里。
合理运用自动释放池,可降低应用程序的内存峰值。(需要消耗大量的资源,内存,CPU的是重量级,量级主要是看对容器的依赖性所决定的,依赖性越小,越轻量)
@autoreleasepool这种新式写法能创建出更为轻便的自动释放池。
35 用僵尸对象调试内存管理问题 总结:
运行期系统如果发现环境变量NSZombieEnabled = “YES”;那么就会吧其dealloc方法调配成zombie的版本。
系统给每个变为僵尸的类都创建一个对应的新类。给僵尸对象发消息后,系统可由此知道该对象原来所属的类。如果把所有僵尸对象都归到_NSZombie_类里,那原来的类名就丢了。创建新类的工作由运行期函数objc_duplicateClass()完成,会把整个_NSZombie_类的结构拷贝一份,并赋予其新的名字。副本类的超类,实例变量及方法都和复制前相同。还有种做法也能保留旧类名,那就是不拷贝_NSZombie_而是创建继承自 (轻量级的)_NSZombie_的新类,但是用相同的函数完成此功能,其效率不如直接拷贝高。由于_NSZombie_没有实现任何方法,所以发给他的全部消息都要经过“完整的消息转发机制“。
系统会修改对象的isa指针,令其指向特殊的僵尸类,从而使该对象变为僵尸对象。僵尸类能响应所有的选择子。响应方式为:打印一条包含消息内容及其接收者的消息,然后终止程序。
36 不要使用retainCount 总结:
retain count永远不会返回0。@“somestring”保留计数是2的64-1 @1保留计数是2的63-1 @3.141f 。单例对象的保留计数绝对不会变,这种对象的保留及释放都是空操作。即便两个单例对象之间,其保留计数也各不相同。永远不要使用保留计数。
任何给定时间上的“绝对保留计数”都无法反映对象生命期的全貌。
引入ARC之后,retainCount就正式废止了,在ARC下调用该方法会导致编译器报错。