从方法返回新创建的对象时需要自动释放池。例如考虑这段代码:
- (NSString *)messageOfTheDay {
return [[NSString alloc] initWithFormat:@"Hello %@!", self.username];
}
在方法中创建的字符串的保留计数为1。现在谁来平衡保留人数和释放人数呢?
方法本身?不可能,它必须返回创建的对象,因此它一定不能在返回之前释放它。
方法的调用者?调用者不希望检索需要释放的对象,方法名称并不意味着创建了一个新对象,它仅表示已返回一个对象,并且此返回的对象可能是需要释放的新对象,但它可能会成为一个现有的没有的。该方法返回的结果甚至可能取决于某些内部状态,因此调用方无法知道它是否必须释放该对象,并且不必理会。
如果调用者必须始终按约定释放所有返回的对象,则必须始终保留每个新创建的对象,然后再从方法中将其返回,并且一旦超出范围,调用者就必须释放该对象,除非它再次返回。在许多情况下,这将是非常低效的,因为如果调用者不会总是释放返回的对象,则在许多情况下可以完全避免更改保留计数。
这就是为什么有自动释放池的原因,因此第一种方法实际上将成为
- (NSString *)messageOfTheDay {
NSString * res = [[NSString alloc] initWithFormat:@"Hello %@!", self.username];
return [res autorelease];
}
调用autorelease
对象会将其添加到自动释放池中,但是将对象添加到自动释放池中到底意味着什么呢?好吧,这意味着告诉您的系统“ 我希望您为我释放该对象,但是稍后,而不是现在;它的保留计数需要通过释放来平衡,否则内存会泄漏,但是我自己不能这样做现在,由于我需要使对象保持活动状态超出当前范围,并且调用者也不会为我做任何事情,因此它不知道需要执行此操作,因此将其添加到池中并在清理完之后池,也为我清理我的物品。 ”
使用ARC,编译器会为您决定何时保留对象,何时释放对象以及何时将其添加到自动释放池中,但是仍然需要自动释放池的存在,以便能够从方法中返回新创建的对象而不会泄漏内存。Apple刚刚对生成的代码进行了一些漂亮的优化,这些优化有时会在运行时消除自动释放池。这些优化要求调用者和被调用者都使用ARC(请记住,混合使用ARC和非ARC是合法的,并且也得到正式支持),并且如果确实如此,则只能在运行时知道。
考虑以下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];
如您所见,Apple消除了atuorelease,从而消除了销毁池时延迟释放的对象以及安全性。要了解有关如何实现以及幕后实际情况的更多信息,请查看此博客文章。
现在到一个实际的问题:为什么要用一个 @autoreleasepool
?
对于大多数开发人员来说,今天在他们的代码中使用此构造仅剩下一个原因,那就是在适用的情况下将内存占用保持在较小的水平。例如,考虑以下循环:
for (int i = 0; i < 1000000; i++) {
// ... code ...
TempObject * to = [TempObject tempObjectForData:...];
// ... do something with to ...
}
假设每次致电都会tempObjectForData
创建一个新的TempObject
,并返回自动释放。for循环将创建这些临时对象中的100万个,这些临时对象全部收集在当前的autoreleasepool中,并且仅当销毁该池后,所有临时对象也会被销毁。在此之前,您在内存中只有一百万个这些临时对象。
如果您改为这样编写代码:
for (int i = 0; i < 1000000; i++) @autoreleasepool {
// ... code ...
TempObject * to = [TempObject tempObjectForData:...];
// ... do something with to ...
}
然后,每次for循环运行时都会创建一个新池,并在每次循环迭代结束时将其销毁。这样一来,尽管循环运行了100万次,但随时最多有一个临时对象在内存中徘徊。
过去,在管理线程(例如使用NSThread
)时,您通常还必须自己管理autoreleasepools,因为只有主线程会自动为Cocoa / UIKit应用程序提供一个autorelease池。但是,今天这几乎已经成为历史,因为今天您可能根本不会使用线程。您将使用GCD DispatchQueue
或NSOperationQueue
,这两个都为您管理一个顶级自动释放池,该池在运行块/任务之前创建,并在完成后销毁。