从方法返回新创建的对象需要自动释放池。例如,考虑下面这段代码:
- (NSString *)messageOfTheDay {
return [[NSString alloc] initWithFormat:@"Hello %@!", self.username];
}
方法中创建的字符串的保留计数为1。现在谁来平衡保留和释放?
方法本身?不可能,它必须返回创建的对象,所以在返回之前不能释放它。
方法的调用者?调用者并不期望检索需要释放的对象,方法名称并不意味着创建了一个新对象,它只是说返回了一个对象,这个返回的对象可能是一个需要释放的新对象,但也可能是一个不需要释放的现有对象。方法所返回的内容甚至可能取决于某些内部状态,因此调用者无法知道它是否必须释放该对象,也不应该关心。
如果调用方必须按照约定总是释放所有返回的对象,那么每个不是新创建的对象总是必须在从方法返回它之前被保留,并且一旦它超出作用域,调用方就必须释放它,除非它再次返回。在许多情况下,这将是非常低效的,因为在许多情况下,如果调用者并不总是释放返回的对象,则可以完全避免更改保留计数。
这就是为什么会有自动释放池,所以第一个方法实际上会变成
- (NSString *)messageOfTheDay {
NSString * res = [[NSString alloc] initWithFormat:@"Hello %@!", self.username];
return [res autorelease];
}
Calling autorelease on an object adds it to the autorelease pool, but what does that really mean, adding an object to the autorelease pool? Well, it means telling your system "I want you to to release that object for me but at some later time, not now; it has a retain count that needs to be balanced by a release otherwise memory will leak but I cannot do that myself right now, as I need the object to stay alive beyond my current scope and my caller won't do it for me either, it has no knowledge that this needs to be done. So add it to your pool and once you clean up that pool, also clean up my object for me."
With ARC the compiler decides for you when to retain an object, when to release an object and when to add it to an autorelease pool but it still requires the presence of autorelease pools to be able to return newly created objects from methods without leaking memory. Apple has just made some nifty optimizations to the generated code which will sometimes eliminate autorelease pools during runtime. These optimizations require that both, the caller and the callee are using ARC (remember mixing ARC and non-ARC is legal and also officially supported) and if that is actually the case can only be known at runtime.
考虑以下ARC代码:
// Callee
- (SomeObject *)getSomeObject {
return [[SomeObject alloc] init];
}
// Caller
SomeObject * obj = [self getSomeObject];
[obj doStuff];
系统生成的代码,可以表现为如下代码(这是允许你自由混合ARC和非ARC代码的安全版本):
// Callee
- (SomeObject *)getSomeObject {
return [[[SomeObject alloc] init] autorelease];
}
// Caller
SomeObject * obj = [[self getSomeObject] retain];
[obj doStuff];
[obj release];
(注意调用者中的保留/释放只是一个防御性的安全保留,它不是严格要求的,没有它代码会完全正确)
或者它可以像下面的代码一样,在运行时检测到两者都使用了ARC:
// Callee
- (SomeObject *)getSomeObject {
return [[SomeObject alloc] init];
}
// Caller
SomeObject * obj = [self getSomeObject];
[obj doStuff];
[obj release];
正如你所看到的,苹果消除了aturelease,因此当池被破坏时,也延迟了对象释放,以及安全保留。要了解更多关于这是如何实现的,以及幕后到底发生了什么,请查看这篇博客文章。
现在来看看真正的问题:为什么要使用@autoreleasepool?
对于大多数开发人员来说,现在在他们的代码中使用这个结构的原因只有一个,那就是在适用的地方保持较小的内存占用。例如,考虑这个循环:
for (int i = 0; i < 1000000; i++) {
// ... code ...
TempObject * to = [TempObject tempObjectForData:...];
// ... do something with to ...
}
假设对tempObjectForData的每次调用都可能创建一个新的TempObject,该TempObject会自动释放返回。for循环将创建一百万个这样的临时对象,它们都被收集到当前的autoreleasepool中,只有当这个池被销毁时,所有的临时对象也被销毁。在此之前,内存中有一百万个这样的临时对象。
如果你这样写代码:
for (int i = 0; i < 1000000; i++) @autoreleasepool {
// ... code ...
TempObject * to = [TempObject tempObjectForData:...];
// ... do something with to ...
}
然后,每次for循环运行时都会创建一个新池,并在每次循环迭代结束时销毁。这样,尽管循环运行了一百万次,但在任何时候最多只有一个临时对象挂在内存中。
在过去,当你管理线程(例如使用NSThread)时,你经常不得不自己管理自动释放池,因为只有主线程自动有一个Cocoa/UIKit应用程序的自动释放池。然而,这几乎是今天的遗留问题,因为今天你可能不会使用线程开始。你可以使用GCD的DispatchQueue或NSOperationQueue,这两个都为你管理一个顶级的自动释放池,在运行块/任务之前创建,一旦完成就销毁。