【译】苹果官方手册:高级内存管理编程手册4:使用自动释放池块
自动释放池块提供了一种机制,你可以借助这种机制放弃对象的所有权,但又不会造成对象的立即销毁(例如当你从一个方法中返回对象时)。一般情况下,你不需要创建自己的自动释放池块,但也存在一些你不得不创建,或最好创建的情况。
关于自动释放池块
自动释放池块就是被@autoreleasepool标记的代码块。就像下面这样:
@autoreleasepool {
// Code that creates autoreleased objects.
}
在自动释放池块的末尾,所有收到过autorelease消息的对象的release方法会被调用——在代码块中所有收到autorelease消息的对象都会收到release消息。
就像其他的代码块一样,自动释放池块可以嵌套:
@autoreleasepool {
// . . .
@autoreleasepool {
// . . .
}
. . .
}
(这种代码并不常见,通常情况是位于某代码文件中自动释放池块内的代码会调用另一个代码文件中不同的自动释放池块内的代码。)当你在某个自动释放池块里给某个对象发送了autorelease消息,那么对应的release消息便会在这个自动释放池块的末尾发送给该对象。
Cocoa总是期望你的代码会放在一个自动释放池块内运行,否则本该自动释放的对象不会被释放,你的应用将会有内存泄漏。(如果你在一个自动释放池块的外边给对象发送了autorelease消息,Cocoa将会记录一条对应的错误信息。)AppKit和UIKit框架在每个事件循环迭代(例如鼠标或手指的轻点)过程中处理一次自动释放池块。所以通常你不需要亲手创建一个自动释放池块,或者说真正看到用来创建自动释放池块的代码。但以下三种情况则可能会需要你使用自己的自动释放池块:
- 如果你所写的程序不是基于UI框架的时候,比如控制台工具。
- 如果你写的循环中包含了大量临时对象的创建。
你可能会在循环代码块内使用自动释放池块,从而在下次循环进行之前释放这些对象。在循环中使用自动释放池块会帮助改善应用程序的最高内存占用率。 - 如果你使用了多个线程。
你必须在线程开始运行之前创建自己的自动释放池块;否则,你的应用程序将会内存泄漏。(详细讨论参考下文自动释放池块与线程。)
使用局部自动释放池块降低最大内存占用率
一些程序会创建大量自动释放的临时对象。这些对象会在代码块结束之前增加程序的内存占用率。有时候,在当前时间循环迭代结束之前累积临时变量不会导致内存资源的过分消耗,但也有时候,巨大数量的临时对象会占据客观的内存资源,这时你可能会希望对象尽可能快地被销毁。在后者的情况下,你可能会需要创建自己的自动释放池块。在代码块的末尾,这些临时对象会被释放,通常这些对象的释放会降低程序的内存使用率。
下面的例子展示了在for循环中使用自动释放池块的例子:
NSArray *urls = <# An array of file URLs #>;
for (NSURL *url in urls) {
@autoreleasepool {
NSError *error;
NSString *fileContents = [NSString stringWithContentsOfURL:url
encoding:NSUTF8StringEncoding error:&error];
/* Process the string, creating and autoreleasing more objects. */
}
}
for循环每次循环处理一个文件。所有的对象(例如fileContents)在自动释放池块内会收到一条autorelease消息,从而在自动释放池块的末尾被释放。
在自动释放池块之后,你应该意识到所有代码块内的对象都被“释放掉”了。不要给这些对象发送消息,或将它们返回给当前方法的调用者。如果你确实需要在自动释放池块之外使用一个临时对象,可以在代码块内给该对象发送一条retain消息,并在代码块之外再给它发送autorelease消息。示例如下:
– (id)findMatchingObject:(id)anObject {
id match;
while (match == nil) {
@autoreleasepool {
/* 进行一次搜索,从而产生大量临时对象。 */
match = [self expensiveSearchForObject:anObject];
if (match != nil) {
[match retain]; /* 保持match对象。 */
}
}
}
return [match autorelease]; /* 返回match并使其自动释放 */
}
在自动释放池块内给match发送retain消息,并在自动释放池块外边给它发送autorelease消息。这扩展了match的生存空间,并使其可以在返回给findMatchingObject:方法的调用者后可以被正常使用。
自动释放池块与线程
<p id="jump1"></p>
Cocoa应用程序的每个线程都维持它们自己的自动释放池块栈。如果你正在写一个只使用了基础类库的程序,或者当你在分发一个新的线程的时候,你需要创建自己的自动释放池块。
如果你的程序或者线程是长时间活跃的,并且有可能会生成大量自动释放对象,那么你应该使用自动释放池块(就像AppKit和UIKit在主线程中所做的那样);否则,自动释放对象会累积,使内存占用率持续增加。如果你分发的线程里没有调用Cocoa的方法,那么你不需要使用自动释放池块。
注意: 如果你使用POSIX线程API来创建次要线程,而不是使用NSThread的话,除非Cocoa处在多线程模式,否则你不能使用Cocoa。Cocoa只有在分发了第一个NSThread对象后才会进入多线程模式。为了在POSIX次要线程中使用Cocoa,你的程序必须先分发至少一个NSThread线程对象——这个线程可以立即退出。你可以调用NSThread的类方法isMultiThreaded来判断Cocoa是不是处在多线程模式。