NSAutoreleasePool自动释放池是如何工作的?

96

据我理解,任何使用allocnewcopy创建的对象都需要手动释放。例如:

int main(void) {
    NSString *string;
    string = [[NSString alloc] init];
    /* use the string */
    [string release];
}

我的问题是,这个方案是否同样有效?

int main(void) {
    NSAutoreleasePool *pool;
    pool = [[NSAutoreleasePool alloc] init];
    NSString *string;
    string = [[[NSString alloc] init] autorelease];
    /* use the string */
    [pool drain];
}
7个回答

69

是的,你的第二段代码是完全有效的。

每次调用-autorelease方法时,该对象都会被添加到最内层的autorelease池中。当池被释放时,就会向池中所有对象发送-release消息。

自动释放池只是一个方便的功能,允许你将-release操作推迟到“稍后”执行。这个“稍后”可以发生在几个地方,但在Cocoa GUI应用程序中最常见的是在当前运行循环周期结束时。


5
如果我没有循环,当前运行循环周期的结束在哪里? - Thanks
24
“outer-most” 应该翻译为 “最外层”,而非“最内层”? - Mike Weller
“an object” 应该是 “一个 NSObject 或 NSProxy 的子类对象,并且不覆盖 -autorelease 方法”。 - user142019
1
编辑:更改为最内层。 - chakrit
1
重要提示:如果您使用自动引用计数(ARC),则不能直接使用自动释放池。相反,您需要使用@autoreleasepool块。来自https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSAutoreleasePool_Class/Reference/Reference.html - Md Mahbubur Rahman
感谢!运行循环并不是一个循环。它不像forwhile那样是一种语言结构。相反,它是一种调度机制,用于管理线程内的不同执行状态和事件。 - user529758

37

NSAutoreleasePool: drain vs. release

由于drainrelease的功能似乎引起了混淆,因此在此进行澄清可能是值得的(尽管这在文档中已经有说明...)。

严格来说,从大局的角度来看,drainrelease并不等同:

在基于引用计数的环境中,drain执行与release相同的操作,因此从这个意义上讲,两者是等效的。强调一下,这意味着如果您使用drain而不是release,您不会泄漏内存池。

在垃圾回收环境中,release是一个无操作(no-op)。它没有任何影响。drain则包含一个提示,提示回收器如果需要可以进行“收集”操作。因此,在垃圾回收环境中,使用drain有助于系统平衡回收扫描。


4
基本上不可能将NSAutoreleasePool泄露。这是因为池的操作类似于堆栈。实例化一个池将该池推到该线程的自动释放池堆栈的顶部。-release方法会导致该池从堆栈中弹出,并且也会弹出任何被推到其上方但由于某种原因未弹出的池。 - johne
7
这与我所写的有何关联? - mmalc
2
我喜欢他花时间将AND加粗的方式。啪! - Billy Gray

17

正如已经指出的那样,你的第二个代码片段是正确的。

我想建议一种更为简洁的使用自动释放池的方法,它适用于所有环境(引用计数,垃圾回收,ARC),并且还避免了drain/release混淆:

int main(void) {
  @autoreleasepool {
    NSString *string;
    string = [[[NSString alloc] init] autorelease];
    /* use the string */
  }
}

请注意上面的示例中的@autoreleasepool块。这在这里有文档记录。


2
请注意,在ARC中不允许使用autorelease。 - dmirkitanov
1
需要澄清的是,在ARC中必须使用@autoreleasepool块。 - Simon

7
不,你错了。文档明确说明,在非GC环境下,“-drain”等同于“-release”,这意味着NSAutoreleasePool不会泄漏。

我曾经想知道为什么Xcode会生成带有-drain的代码,如果那是这种情况的话。我使用-drain是因为我认为它与Xcode生成的代码基于-release是等效的。 - James Sumners
1
基本上不可能“泄漏”NSAutoreleasePool:http://developer.apple.com/mac/library/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmAutoreleasePools.html#//apple_ref/doc/uid/20000047-997594 - johne

0
将autorelease发送给一个对象,会延长该对象的生命周期,至少直到池本身被排空(如果该对象随后被保留,则可能更长时间)。一个对象可以多次放入同一个池中,在这种情况下,它会收到每次放入池中时的释放消息。

0

-2

是和不是。如果您在垃圾回收(而非内存管理)环境下运行此代码,使用 drain 而非 release 会释放字符串内存但“泄漏”NSAutoreleasePool对象到内存中。这种“泄漏”使得 NSAutoreleasePool 实例像 GC 下没有强指针的任何其他对象一样“不可访问”,并且该对象将在下次 GC 运行时清除,这可能直接发生在调用-drain之后:

drain

在垃圾回收环境中,如果自上次收集以来分配的内存大于当前阈值,则触发垃圾回收;否则,行为类似于 release。 ... 在垃圾回收环境中,此方法最终调用 objc_collect_if_needed

否则,它与非 GC 下的 -release 行为类似。正如其他人所述,-release 在 GC 下是无操作,因此确保池在 GC 下正常工作的唯一方法是通过 -drain,而在非 GC 下,-drain 的工作方式与 -release 完全相同,并且可以说其功能更加清晰明了。

我应该指出你的说法“任何使用new、alloc或init调用的东西”不应该包括“init”(但应该包括“copy”),因为“init”并不分配内存,它只是设置对象(构造函数方式)。如果你收到了一个alloc'd对象,并且你的函数只是像这样调用了init,那么你就不会释放它:
- (void)func:(NSObject*)allocd_but_not_init
{
    [allocd_but_not_init init];
}

这不会消耗比你已经拥有的更多的内存(假设 init 不实例化对象,但无论如何你都不需要负责这些)。


你对drain的信息并不完全正确,所以我不太愿意把这个答案设为被接受的状态。请参考http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSAutoreleasePool_Class/Reference/Reference.html#//apple_ref/doc/uid/20000051-SW5进行更新,之后我会重新接受答案。 - James Sumners
回复中有什么不准确的地方?在垃圾回收环境中(如所述),drain 不会删除 AutoReleasePool,因此除非使用 release,否则您将泄漏内存。我列出的引用直接来自于 drain 的文档。 - Loren Segal
1
Loren:在GC下,-[NSAutoreleasePool drain]将触发一次垃圾回收。-retain、-release和-autorelease都被垃圾回收器忽略了;这就是为什么在GC下需要使用-drain来处理autorelease池的原因。 - Chris Hanson
在“drain”的文档中: 在受控内存环境中,这与调用“release”相同。 因此,如果您使用“drain”,将不会泄漏内存。 - mmalc
在垃圾回收环境中,-[NSAutoreleasePool release] 是无效操作。而 -[NSAutoreleasePool drain] 则适用于引用计数和垃圾回收环境。 - Jonathan Sterling

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接