Chapter 5. Memory Management
<br />
Item 32: Beware of Memory Management with Exception-Safe Code
<br />
这一节讲的是使用exception时存在的内存管理问题。
在Item21讲NSError的使用时已经提到了,OC的设计并不是exception-safe的,因此不能普遍地使用exception,而只应该在发生严重问题的时候才用。大多数情况使用NSError是更好地选择。
具体来说,在手动管理引用计数的时候,避免内存泄露的写法是这样的:
EOCSomeClass *object;
@try {
object = [[EOCSomeClass alloc] init];
[object doSomethingThatMayThrow];
}
@catch (…) {
NSLog(@“error occurred”);
}
@finally {
[object release];
}
关键在于要添加一个@finally块,这是一个不管是否抛出异常都会到达的块,所以把release放在这里,保证object的释放。如果没有这个块,把release放在@try里,那么如果释放前抛出了异常,释放这一句就不会被调用,内存泄露就会产生。
如果在ARC环境,release就不能手动调用了。但需要注意的是,和普通情况不同,此时系统默认情况下也不会自动帮我们调用,必须开启-fobjc-arc-exceptions编译器标志以后,ARC才可以生成这种情况下的附加代码。但是会一定程度影响运行效率。
<br />
Item 33: Use Weak References to Avoid Retain Cycles
<br />
这一节讲用弱引用打破保留环。
篇幅比较短,内容我也比较熟悉,好像没有什么特别要记下来的。实际中最常见的场景就是用block的时候要小心,什么时候需要定义weakSelf。如果block直接对self引用比较容易看出来,但是环里有两个以上的对象的时候就不是很容易看出来,比如block里引用了一个已经引用self的对象。
__unsafe_unretained和__weak都能有打破环的效果,因为都并不保留对象,但是因为存在野指针的问题,__weak是更推荐的做法,更安全。
<br />
Item 34: User Autorelease Pool Blocks to Reduce High-Memory Waterline
<br />
这一节讲通过新建autorelease pool来压低内存的峰值。
“The high-memory waterline refers to the highest memory footprint of an application during a certain period.” 一开始不太懂这个waterline到底指的什么,根据这句理解是程序运行中的内存峰值。
如果对对象调用autorelease而不是release,就会使对象多存活一段时间,到下一个event loop才会调用一次release,这里是指只是用系统自带的那个写在main函数的自动释放池的情况。但是如果所有的对象都等到这时再释放,内存就会一直积累,峰值就会很高。为了避免这种情况,当出现占用内存多但是又可以用完就释放的对象时,可以手动创建一个自动释放池来使它提前释放,比如这样:
NSArray *databaseRecords = /*…*/
NSMutableArray *people = [NSMutableArray new];
for (NSDictionary *record in databaseRecords) {
@autoreleasepool {
EOCPerson *person = [[EOCPerson alloc] initWithRecord:record];
[people addObject:person];
}
}
这个场景假设的是需要新建的person对象非常多。如果不采用自动释放池,这些新建的person对象就会一直存在到下一个event loop,而采用这种写法以后,在这个自动释放池的末尾这些临时对象就可以被回收了,这样内存峰值就及时降下来了。
<br />
Item 35: Use Zombies to Help Debug Memory-Management Problems
<br />
这一节讲的是用Zombie Objects来调试程序。
这个东西我不太熟悉,也没有见过。不过原理还是比较好理解。对象在被释放以后,原来存放对象的那块内存会被标记为“可重用”,这样就可以重新被分配做他用了。但是这个过程不是立即执行的,具体什么时候执行并不能确定。所以这块内存可能仍有东西在占用,如果给它发消息有时甚至还能相应,但是不一定是我们希望的那个对象的响应,这样就会有奇怪的Bug出现。而比较理想的情况是,一旦内存被释放了,不管有没有被重新用掉,我们都能得知这一信息,如果给它发消息,能提示我们是在给已经释放的对象发消息,利于我们调试。
Zombie Object就能实现这一功能。默认是不打开NSZombieEnabled环境变量的,怎么打开文中写了,我找了一下,真的有诶←_←。
僵尸对象的原理是,在NSObject执行dealloc时,如果发现打开了NSZombieEnabled环境变量,就通过method swizzling(Item13有讲过)把dealloc的代码变了,多加了一部分,不仅把对象所属的类变成NSZombie了,而且还自动加上了原类名作为后缀,就像_NSZombie_OriginalClassName
这种形式。此时如果再向这个对象发送消息,程序会判断消息的接收者的类型,如果是以NSZombie为前缀,就不会执行这个消息,而是打印一条错误的信息出来,提示“message sent to deallocated instance”,并且会显示对象销毁前的类名。
使用Zombie Object是不会把内存释放的,只是把需要释放的内存改了所属类。但是只是在调试时会使用,所以不会引起实际的效率问题。
<br />
Item 36: Avoid Using retainCount
<br />
用了整整一节来嫌弃retainCount这个方法...
结论就是永远都不要用。原因的话,其实这个方法的reference已经写的比较明白了:
This method is of no value in debugging memory management issues. Because any number of framework objects may have retained an object in order to hold references to it, while at the same time autorelease pools may be holding any number of deferred releases on an object, it is very unlikely that you can get useful information from this method.
总的来说,就是因为引用计数是一个非常动态的过程,特别是有自动释放池存在的情况下,计数的增减有实际意义,而绝对值并没有实际意义。不过文中给了更为详细例子。比如有时系统会做优化,对象在引用计数为1时就可能被回收。再比如,单例对象的引用计数永远都很大而且不变。再比如,某个调用方法内部可能自行保留或者释放了对象,而外面看不出来,使得到的结果和想象得不一致。所以调用这个方法得不到任何有效的信息,结论就是,永远不要用。
网友评论