Cocoa 高级内存管理编程指南
高级内存管理编程指南
本文档翻译自apple官方文档 Advanced Memory Management Programming Guide。最近相对空闲,决定好好读读文档,其实自从苹果推出ARC以来,Cocoa中的内存管理不再是难题了,但是了解ARC背后的原理还是很有价值的,错误的地方欢迎大家指正。后续如果还有时间,再继续翻译Transitioning to ARC Release Notes这篇介绍ARC的文档。
Contents
1 关于内存管理
应用程序内存管理负责在程序运行时分配和释放内存。一个良好的应用程序总是 尝试使用尽量少的内存。在Objective-C中,也有其管理和分配有限内存的方式 方法。当你完成本份文档,你应当能够具备内存管理相关的知识,包括显式的管 理对象的生命周期以及释放不再使用的对象。
尽管内存管理通常都是指的单个对象的管理,但你的目标应当是对所有分配对象 有个整体的把握。你要保证,内存中不会有你实际需要以外的对象存在。
1.1 概览
Objective-C提供了两种方法来进行内存管理:
- 本文档所描述的,手动retain-release,简称MRR。开发者手动管理内存,负 责对象的分配和释放。这种方法由基础类NSObject通过引用计数的方式来实现。
- 自动引用计数,简称为ARC。这种方式使用与MRR同样的引用计数方法,但它 是在编译时自动插入合适的内存管理方法。在新的工程中,强烈推荐你是用 ARC。如果使用ARC,你不需要了解本文档所描述的内存管理细节。关于ARC的 更多细节,请参阅Transitioning to ARC Release Notes。
如果你打算开发iOS应用,你必须使用显式的内存管理方式(根据本文档描述)。 而如果你打算开发库,插件或共享代码,这些代码可能运行在包含GC(垃圾回收) 或不包含GC的代码中,请确保在Xcode中,关闭GC,进行测试。
1.1.1 防止内存相关问题的良好实践
主要有两类问题导致内存管理相关问题:
- 释放或覆写仍然在使用的内存数据
通常这会导致内存破坏从而引发应用程序崩溃,更糟的是,可能会损坏用户数 据。
- 未释放不再使用的数据导致内存泄漏
内存泄漏是指没有释放不再使用的内存。内存泄漏会导致应用程序占用越来越 多的内存,这会导致整个系统性能低下,而在iOS下,你的应用程序会自动被 终止。
过度关注引用计数实现内存管理的实现细节,而忽略了内存管理的目的,会显得 非常低效。作为开发者,应当更多的关注对象的所有权以及对象视图(object ownership and object graphs)
1.1.2 使用分析工具来调试内存问题
可以使用Xcode中的Clang静态分析工具在运行时分析和查找代码中出现的问题。
如果内存问题无论如何都会出现,你能够使用以下工具和技术来进行问题诊断:
- 大多数工具和技术在技术文档TN2239:iOS Debugging Magic中描述。例如可 以使用NSZombie来查找重复释放的对象方法。
- 使用Instruments来追踪引用计数事件以便查找内存泄漏的问题。查看 “Collecting Data on Your App”。
2 内存管理方法
使用引用计数来进行内存管理的基本模型是由NSObject中Protocol定义的一套通 用的函数来实现的。NSObject也定义了dealloc函数,该函数将在对象真正被释 放时自动调用。本文档描述了所有在Cocoa中需要了解的内存管理基本知识,并 提供了一些正确正确进行内存管理的例子。
2.1 基本内存管理原则
内存管理的基本模型是基于对象的所有权。任意对象可能有一个或多个所有者。 只要一个对象有所有者,它就会在内存中一直存在。如果这个对象没有了所有者, 那么系统就会自动将其销毁。为了弄清楚什么时候你拥有对象什么时候没有, Cocoa建立了以下原则:
- 只要是你创建的对象,你都拥有其所有权
调用方法,方法名以 alloc, new, copy, mutableCopy 作为为开头 的,所生成的对象,你都拥有其所有权(例如:alloc, newObject, mutableCopy等方法)。
- 可以通过对对象执行retain操作,来获得其所有权
在方法中获得的对象应当保证其始终有效,并且当其需要返回这个这个对象时, 也要保证这个对象始终有效。可以在以下两种情况下使用retain:
- 在实现存取方法或初始化方法时,需要获得某个对象所有权,并将这个对 象作为属性值(property)保存;
- 防止对象由于某些调用产生的副作用,导致该对象不可用(参见后续:避 免销毁你正在使用的对象一章)
- 如果不再使用某个对象,要负责释放自己对该对象的所有权
开发者通过调用release或autorelease方法来释放自己对这个对象的所有权。 在Cocoa中的术语中,放弃一个对象的所有权就是指的释放(release)这个对 象。
- 不能释放不属于自己的对象的所有权
这条规则是以上规则的推论,为了强调其重要性,在这里明确列出来。
2.1.1 一个简单的例子
为了说明这些规则,来看一看以下这个例子
{ Person *aPerson = [[Person alloc] init]; // ... NSString *name = aPerson.fullName; // ... [aPerson release]; }
Person对象是通过alloc方法创建,因此当不再使用该对象时,必须要对其发送 一个release消息来释放对它的所有权。没有对person对象的name对象调用任何 产生所有权的方法,因此也不需要对其调用release方法来释放其所有权。值得 注意的是,这个例子使用的是release方法,而不是autorelease方法。
2.1.2 使用autorelease来延迟release操作
如果需要推迟release操作,可以使用autorelease。这种情况通常出现在一个方 法需要将对象作为返回值返回时。举例来说,请看下面这个程序:
- (NSString *)fullName { NSString *string = [[[NSString alloc] initWithFormat:@"%@ %@", self.firstName, self.lastName] autorelease]; return string; }
你通过alloc生成了NSString对象,获得了其所有权。为了遵守内存管理原则, 你必须失去对其引用前,必须释放其所有权。如果你使用release操作,这个 NSString对象会在返回前被释放(这样这个方法将返回一个非法对象)。如果使 用autorelease操作,则表示,在失去对这个对象的引用后,你准备释放其所有 权,但对于这个函数的调用者来说,它仍然能够使用这个返回对象。
你也可以这样来实现这个方法:
- (NSString *)fullName { NSString *string = [NSString stringWithFormat:@"%@ %@", self.firstName, self.lastName]; return string; }
按照基本规则,你并不拥有通过stringWithFormat:方法返回的NSString对象的 所有权,因此,你能够安全的在这个方法中返回这个对象。
作为对比,下面这个实现方法是错误的:
- (NSString *)fullName { NSString *string = [[NSString alloc] initWithFormat:@"%@ %@", self.firstName, self.lastName]; return string; }
根据命名规则,调用者调用这个方法获得这个NSString对象,调用者并没有这个 对象的所有权,它也就无须对这个对象执行release操作,后续操作,这个对象 也就泄漏了。
2.1.3 你不拥有通过引用返回的对象的所有权
特别的,在cocoa中,有的方法会通过引用来返回一个对象(举例来说,这些返 回值的参数类型为 ClassName ** 或 id *)。最常见的模式是当某个操作发生 错误,返回包含错误信息的NSError对象,如: initWithContentsOfURL:options:error: (NSData) 和 initWithContentsOfFile:encoding:error: (NSString)。
在这种情况下,当你调用任意一个这样的方法,你没有创建NSError对象,你也 就没有这个对象的所有权,因此你也无须对这个对象执行release操作,例如:
NSString *fileName = <#Get a file name#>; NSError *error; NSString *string = [[NSString alloc] initWithContentsOfFile:fileName encoding:NSUTF8StringEncoding error:&error]; if (string == nil) { // Deal with error... } // ... [string release];
2.2 实现dealloc方法来释放对象的所有权
在NSObject类中定义了一个dealloc方法,当对象没有任何拥有者的时候并且其 内存被回收时,这个方法会自动的被调用。在Cocoa中,这被称为freed或 deallocated。这个方法的作用是释放这个对象所拥有的对象内存,处理该对象 拥有的任意资源,包括任何它拥有所有权的实例变量。
下面这个例子展示了在Person类中应当如何实现dealloc方法:
@interface Person : NSObject @property (retain) NSString *firstName; @property (retain) NSString *lastName; @property (assign, readonly) NSString *fullName; @end @implementation Person // ... - (void)dealloc [_firstName release]; [_lastName release]; [super dealloc]; } @end
*重要*:绝不要直接调用其他对象的dealloc方法。在实现这个方法时,必须在最 后调用父类(super)的dealloc方法。由于当应用程序结束时,对象可能不会收 到dealloc消息,不能把系统资源的管理同对象生命周期绑定起来;查看Don't Use dealloc to Manage Scarce Resources" (page 17)。当程序退出时,系统 会自动清理这个程序所占用的所有资源,因此让系统在退出时清理系统资源比在退出 时调用所有内存管理方法执行内存操作更为有效。
2.3 Core Foundation使用类似的但不尽相同的内存管理规则
Core Foundation使用类似的内存管理规则(查阅文档Memory Management Programming Guide for Core Foundation)。当Cocoa和Core Foundation的命 名规则不尽相同。特别是Core Foundation的创建规则(查看"The Create Rule" in Memory Management Programming Guide for Core Foundation)不适用于返 回Objective-C对象的方法。例如,下面这段代码,你就不需要释放myInstance对象 的所有权:
MyClass *myInstance = [MyClass createInstance];
3 内存管理实践
尽管在内存管理原则一章描述的基本概念非常明确,仍然有一些最佳实践可以让 你更好的掌握内存管理,并保证你的程序使用尽可能少的资源且是可靠和健壮的。
3.1 使用存取方法来简化内存管理
如果你的类包含一个是类的属性(property),你必须确保任一个对象在被使用 时不会被释放。你必须在这个对象赋值时声明对这个对象的所有权,并保证在随 后的操作中,如果不再使用这个对象,将其释放。
有的时候,始终使用存取方法显得过于沉闷和迂腐,当如果你坚持使用存取方法, 出现内存错误的几率将显著下降。如果你在自己的代码中始终对你的实例变量调 用retain和release方法,基本可以肯定的是,你会在错误方向上越走越远。
考虑下面这个例子,有一个你需要创建的Counter对象:
@interface Counter : NSObject @property (nonatomic, retain) NSNumber *count; @end;
property操作符声明了两个存取方法。通常来说,你应当让编译器来为你合成 (synthesize)这些方法;但更重要的是了解这些存取方法背后的实现原理。
在这个对象get操作中,你只需要返回编译器合成的实例变量,因此没有必要执 行retain和release操作:
- (NSNumber *)count { return _count; }
在set方法中,如下例所示,如果其他代码也遵守同样的内存管理规则来操作 newCount参数,你必须对这个变量执行retain操作,避免其他地方对其进行了操 作,将其释放。你也必须将实例变量原有值执行release操作来释放其所有权。 (在Objective-C中,向nil对象发送任意消息都是允许的,因此即使_count这个 实例变量即使没有初始化,这样做也是可以的。)必须在[newCount retain]操 作后,在执行[_count release]操作,避免可能这两个变量指向的是同一个对象, 这样无意间将其释放了。
- (void)setCount:(NSNumber *)newCount { [newCount retain]; [_count release]; // Make the new assignment. _count = newCount; }
3.1.1 使用存取方法来设置属性值
假设你需要实现一个方法来重置counter对象。你有几个选择。第一个实现是通 过alloc创建一个NSNumber实例,使用完成后使用release来释放它:
- (void)reset { NSNumber *zero = [[NSNumber alloc] initWithInteger:0]; [self setCount:zero]; [zero release]; }
第二个选择,使用快捷构造函数来创建一个新的NSNumber对象。这样就不需要执 行retain或release操作了。
- (void)reset { NSNumber *zero = [NSNumber numberWithInteger:0]; [self setCount:zero]; }
注意,以上两个方法都是使用的存取方法来进行赋值。
下面这个例子在简单情况下基本上是对的,当这个例子尝试避开使用存取方法来 进行存取操作,在某些场景下可能会产生错误(比如,当你忘记retain或 release,又或者内存管理的语法发生了变化)
- (void)reset { NSNumber *zero = [[NSNumber alloc] initWithInteger:0]; [_count release]; _count = zero; }
也要注意的是,如果你使用key-value observing,这样来修改变量是不适合于 KVO编程的。
3.1.2 不要在初始化或dealloc方法中调用存取方法
初始化和dealloc方法中是唯一不应该使用存取方法的地方,使用一个number对 象来将counter初始化为0,你需要这样实现初始化方法:
- init { self = [super init]; if (self) { _count = [[NSNumber alloc] initWithInteger:0]; } return self; }
如果不是counter这个实例变量初始化为固定值,你需要实现一个 initWithCount:方法:
- initWithCount:(NSNumber *)startingCount { self = [super init]; if (self) { _count = [startingCount copy]; } return self; }
由于Counter类有一个实例变量,你也必须实现dealloc方法。这个方法中必须释 放实例变量的所有权,并且,最后还要调用基类的dealloc方法:
- (void)dealloc {
[_count release];
[super dealloc];
}
3.2 使用弱引用来避免retain循环
retain操作创建了一个对这个对象的强引用。只有这个对象的所有强引用都被释 放后,这个对象才能够被销毁。这就有可能形成这样一个问题,就是我们常说的 retain循环:当两个对象互相包含一个执行对方的强引用(可能不是直接引用对 方,可能是通过其他对象形成的一条强引用链)。
图一举例说明了一个潜在的retain循环。Document对象中包含每一页的Page对象 的引用。而每个Page对象又包含一个属性,这个属性用来跟踪自己是属于哪个 document对象。如果Document对象拥有Page对象的强引用,而Page对象也拥有对 Document对象的强引用,这两个对象就再也不能被释放了。Document对象的引用 计数只有当Page对象是否后才能清零,而Page对象也只有在Document对象被销毁 后才能被释放。
解决这个问题的途径是使用弱引用。弱引用是指一种非拥有的对象,源对象只拥 有指向目的对象的引用,但并没有对这个引用执行retain操作。
为了保证对象图(object graph)的完整,必须要有强引用(如果整个关系图中 只有弱引用,page对象或paragraph对象就没有所有者,那么就会自动被销毁)。 Cocoa建立了一个规约:父对象拥有对子对象的强引用,子对象拥有对父对象的 弱引用。
因此,在图一中,Document对象拥有对Page对象的强引用(需要执行retain操 作),而Page对象拥有对Document对象的弱引用(不要执行retain操作)。
在Cocoa中有很多弱引用的例子,包括table data sources,outline view items,notification observers,还有其他如targets与delegates。
在向一个弱引用的对象发送消息时,必须小心。如果在这个消息被销毁后向其发 送消息,程序会崩溃。你必须明确知道什么情况下这个对象是有效的。在大多数 情况下,弱引用对象为了防止循环引用,我们只关心引用它的对象,当它将要销 毁的时候,需要通知引用它的对象这个对象已经被销毁了。举例来说,当你在消 息中心(notification center)注册了一个对象,消息中心保存了对这个对象 的弱引用,当有消息时来时,会通知这个对象。当这个对象被销毁后,你也必须 在消息中心中将其注销,以防止消息中心将消息发送到已经已经不存在的对象上。 同样,当一个代理(delegate)对象被销毁时,你必须在引用该代理的对象中使 用setDelegate:消息,将其设置为nil。一般来说,这些消息都应在对象的 dealloc方法中被调用。
3.3 避免在销毁正在使用的对象
Cocoa的内存管理原则规定,一般返回的对象需要在其调用函数的作用域范围内 一直有效。并且,当需要将这个返回的对象作为当前方法的返回值返回时,也不 用担心它会因为离开作用域被释放掉。对你的程序来说,getter方法返回实例变 量的缓存值,或者计算值是没有关系的。真正重要的是,当你需要使用这个对象 时,它是有效的。
这条规则也有例外,如下所示:
- 当一个对象被基本集合(collection)类删除时:
heisenObject = [array objectAtIndex:n]; [array removeObjectAtIndex:n]; // heisenObject could now be invalid.
当一个对象从这样的集合类中删除时,它会受到一个release消息(注 意不是autorelease)。如果这个集合是这个被删除对象唯一所有者,那么这 个被删除的对象(这个例子中的heisenObject)将会立即被销毁。
- 当“父对象”被销毁时
id parent = <#create a parent object#>; // ... heisenObject = [parent child] ; [parent release]; // Or, for example: self.parent = nil; // heisenObject could now be invalid.
在某些情况下,当你通过一个父对象获得其子对象时,如果释放了这个父对 象导致这个父对象被销毁,并且这个父对象是其子对象的唯一所有者(这个 例子中的heisenObject),那么这个子对象将一起被销毁(假设这个子对象 在父对象的dealloc方法中是收到的autorelease消息)。
为了防止这种情况,当你获得这个子对象(heisenObject)时,应该对其执行 retain操作,当你不再需要这个对象时,执行release操作:
heisenObject = [[array objectAtIndex:n] retain]; [array removeObjectAtIndex:n]; // Use heisenObject... [heisenObject release];
3.4 不要在dealloc方法中使用稀有资源
不要试图在dealloc中管理稀有资源,比如文件描述符,网络描述符,或是缓存 数据。特别的,不要在类中想当然的设计dealloc方法,认为它会在你自认为合 适的时候被调用。由于BUG或是应用程序出现问题等原因,dealloc方法的调用可 能会有所延迟或不被调用。
如果你的类中有控制稀有资源的实例变量,你应当将你的类设计成:当你不再需 要这些资源,通知该实例变量清理该资源,并发送release方法,然后系统自动 调用dealloc方法,来清理资源,这样即便系统没有适时调用dealloc方法,你也 不会因为没有清理资源而造成问题。
如果你想依赖dealloc方法来进行系统资源的内存管理,可能造成如下问题:
- 对象图的顺序依赖性可能遭到破坏
对象图的销毁在内在机制上是无序的,如果一个对象意外autorelease而不是 release掉了,对象销毁顺序就会发生变化,从而导致无法预期的错误。
- 稀有资源未回收
不要认为内存泄露所造成的问题和影响并不是立即和致命的,就忽略这个问 题。如果稀有资源没有在应该释放的时候释放,就可能导致严重的问题。比 如你的应用程序耗尽了系统文件描述符,用户就可能再也无法保存任何数据 了。
- 在错误的线程上执行清理逻辑
如果在非预期的时间被自动释放了,它可能在任意一个线程的自动释放资源 池中被销毁。这种情况很容易就会造成严重的错误。
3.5 集合类拥有它包含对象的所有权
当添加一个对象到集合中(如array,dictionary或者set),这个集合将拥有这 个对象的所有权。当集合删除这个对象时或是集合自身release时,会释放对这 个对象的所有权。因此,如果你想创建一个包含数字的数组,可以采用如下方式:
NSMutableArray *array = <#Get a mutable array#>; NSUInteger i; // ... for (i = 0; i < 10; i++) { NSNumber *convenienceNumber = [NSNumber numberWithInteger:i]; [array addObject:convenienceNumber]; }
这种情况下,你没有调用alloc方法,因此也不用调用release。在例子中创建的 convenienceNumber也不需要调用retain,因为在array添加操作时,会自动执行 这个操作以获得其所有权。
NSMutableArray *array = <#Get a mutable array#>; NSUInteger i; // ... for (i = 0; i < 10; i++) { NSNumber *allocedNumber = [[NSNumber alloc] initWithInteger:i]; [array addObject:allocedNumber]; [allocedNumber release]; }
在这个例子中,你需要在for循环中对allocedNumber发送release消息以释放其 所有权。因为在array添加这个对象时,会自动对其自行retain操作,因此对这 个对象执行了release操作,是不会释放这个对象的。
为了理解这条规则,把你自己想象成集合类的设计者。你必须要保证,在你设计 的集合类中的所有对象都不会平白无故的被释放,因此你必须在添加它们时执行 retain操作。当你要删除这个对象时,为了平衡retain操作,需要对其执行 release,并且在你的集合类的dealloc方法中,应当释放你还拥有的成员对象。
3.6 所有权规则是使用Retain计数来实现的
对象所有权通过引用计数来实现,这也被称为Retain计数,每一个对象都有一个 retain计数。
- 当你创建了一个对象,这个对象的retain计数为1。
- 当你对这个对象发送retain消息,它的retain计数加1。
- 当你对这个对象发送release消息,它的retain计数减1。
当你对这个对象发送autorelease消息,它的引用计数会在当前autorelease块 结束时减1。
- 如果一个对象的引用计数减为0,它将会被销毁。
重要 没有理由显式地询问一个对象它的retain计数是多少(查看retainCount方法)。 这个结果通常是有误导性的,因为你可能会忽略你所关注的对象是在什么情况下 被retain的。在调试内存问题时,你应当只关注你自己的代码严格遵循对象所有 权规则。
4 使用autorelease pool
你可以使用autorelease pool块提供的机制来释放对象的所有权,并避免所有权 立即被释放(例如,当你需要返回一个对象的时候)。通常来说,你并不需要自 己创建autorelease pool块。但也有一些场景下,手动创建也会带来好处。
4.1 关于autorelease pool块
一个autorelease pool块是通过 @autoreleasepool 来标记的,如下所示:
@autoreleasepool { // Code that creates autoreleased objects. }
在autolease pool块结束的地方,那些在这个块中收到过autorelase消息的对象 将会收到release消息–对象在这个块中收到多少次autorelease消息,在这个时 候就会收到多少次release消息。
同其他代码块一样,autorelease pool块也支持嵌套:
@autoreleasepool { // . . . @autoreleasepool { // . . . } . . . }
(上面这个代码例子在实际代码中几乎不会出现,通常来说,在一个源文件中使 用了autorelease pool块,这个块中包含了另一个源文件中创建了autorelease pool块的代码。)在autorelease pool块结束时,一个autorelease消息对应一 个release消息,在对应的pool块中,发送给对应的对象。
Cocoa总是希望代码是在autorelease pool块中执行,否则接收了autorelease消 息的对象将不会被释放,并会造成内存泄漏。(如果你在autorelease pool块之 外对对象发送autorelase消息,Cocoa会在日志中记录错误信息。)AppKit和 UIKit框架会在autorelease pool块中执行消息循环(例如鼠标点击事件等)。 因此通常来说,你不需要自己创建autorelease pool,或关注创建autorelease pool块的代码。但是在以下三种情况下,你可能需要自己创建autorelease pool 块:
- 如果你编写的程序不是基于UI框架,例如命令行程序
- 如果你在一个循环中需要创建很多临时的对象。
这种情况下,你可能需要在循环中创建autorelease pool块,以便在下一次迭 代前使用对象。在循环中使用autorelease pool块可以减少应用程序内存使用 量的峰值。
- 如果你需要创建一个子线程。
你必须在自己创建的子线程的开始执行时,创建autorelease pool块;否则你 的应用可能会出现对象的泄漏。
4.2 使用autorelease pool块来减少内存使用量的峰值
很多程序会创建autorelease的临时对象。这些对象会增加应用程序总的内存占 用值,直到应用程序结束。在很多情况下,在一个消息循环中执行结束前,不断 的创建临时对象,并不会增加系统负担;在某些情况下,你可能需要创建大量的 临时对象,这会增加系统内存占用的峰值。在后一种情况下,你可以自己创建 autorelease pool块,这样在块结束的地方,临时对象就会被释放,这样也就减 少了程序内存占用的峰值。
下面这个例子介绍了如何在for循环中使用autorelease pool块:
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循环中,一次处理一个文件。在这个autorelease pool块中的任意一个收 到autorelease的对象(例如fileContents)都将在这个块结束的时候自动 release。
在autorelease pool块结束后,你应当注意所有autorelease的对象都应在这个 块中使用,不要在块结束后对这个对象发送消息或是将其作为方法的返回值返回。 如果你需要在autorelease pool块结束后使用这个临时对象,你可以在块中对这 个对象执行retain操作,并在块结束后对其执行autorelease操作,如下所示:
– (id)findMatchingObject:(id)anObject { id match; while (match == nil) { @autoreleasepool { /* Do a search that creates a lot of temporary objects. */ match = [self expensiveSearchForObject:anObject]; if (match != nil) { [match retain]; /* Keep match around. */ } } } return [match autorelease]; /* Let match go and return it. */ }
这个例子在autorelease pool块中发送retain消息,并在这个块结束之后对这个 对象发送autorelease消息,来延长对象的生命周期。
4.3 Autorelease Pool 块与线程
在Cocoa应用中,每个线程维护它自己的autorelease pool。如果你是编写是自 己编写Foundation程序或是自己创建线程,你需要自己创建autorelease pool块。
如果你的程序或现场生存周期较长,或是会产生大量的需要autorelease的对象。 你需要使用autorelease pool块(就像在AppKit和UIKit在主线程中所作的一样); 否则,autorelease的对象聚集起来会增加程序总的内存占用量。如果你detatch 一个线程时,没有使用Cocoa调用,你就不需要使用autorelease pool块。
注意 如果你使用POSIX线程API而不是使用NSThread来创建线程,你就不能使用 Cocoa,除非Cocoa是在多线程模式。Cocoa只有在detatch第一个NSThread线程对 象时才会进入多线程模式。为了在POSIX线程中使用Cocoa,你的应用至少需要 detatch一个NSThread对象,可以创建后立即退出。可以使用NSThread的 isMultiThreaded方法来测试Cocoa是否在多线程模式。
5 文档修改历史
略。