为什么ARC仍需要@autoreleasepool?


191

在大多数情况下,对于ARC(自动引用计数),我们根本不需要考虑使用Objective-C对象进行内存管理。不再允许创建NSAutoreleasePool,但是有一个新语法:

@autoreleasepool {
    
}

我的问题是,当我不应该手动释放/自动释放时,为什么会需要这个?


编辑:总结一下我从所有答案和评论中得出的结论:

新语法:

@autoreleasepool { … } 是的新语法

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

[pool drain];

更重要的是:

  • ARC使用autoreleaserelease
  • 为此需要一个自动释放池。
  • ARC不会为您创建自动发布池。然而:
    • 每个Cocoa应用程序的主线程中已经有一个自动释放池。
  • 在两种情况下,您可能想使用@autoreleasepool
    1. 当您处于辅助线程中并且没有自动释放池时,必须进行自己的设置以防止泄漏,例如myRunLoop(…) { @autoreleasepool { … } return success; }
    2. 当您希望创建更多本地池时,如@mattjgalloway在其答案中所示。

1
还有第三种情况:开发与UIKit或NSFoundation不相关的东西。某些使用命令行工具的东西
-Garnik,

Answers:


214

ARC不会摆脱保留,发布和自动发布,它只是为您添加了必需的内容。因此,仍然有保留的调用,仍然有释放的调用,仍然有自动释放的调用,并且仍然有自动释放池。

他们使用新的Clang 3.0编译器和ARC进行的其他更改之一是,将其替换NSAutoReleasePool@autoreleasepool编译器指令。NSAutoReleasePool无论如何,它们总是总是有点特殊的“对象”,并且他们做到了这一点,因此使用一个对象的语法不会与对象混淆,因此它通常更简单一些。

因此,基本上,您需要这样做,@autoreleasepool因为仍然需要担心自动发布池。您只需要担心添加autorelease呼叫。

使用自动释放池的示例:

- (void)useALoadOfNumbers {
    for (int j = 0; j < 10000; ++j) {
        @autoreleasepool {
            for (int i = 0; i < 10000; ++i) {
                NSNumber *number = [NSNumber numberWithInt:(i+j)];
                NSLog(@"number = %p", number);
            }
        }
    }
}

当然,这是一个非常人为的例子,但是如果您没有@autoreleasepool内在的内在for循环那么以后您将释放100000000个对象,而不是每次围绕外部循环释放10000个对象for

更新: 另请参阅此答案-https: //stackoverflow.com/a/7950636/1068248为什么@autoreleasepool与ARC无关。

更新: 我研究了这里发生的事情的内部情况,并将其写在我的博客上。如果在那里看一眼,那么您将确切地看到ARC在做什么,@autoreleasepool编译器将使用新样式以及它如何引入作用域来推断有关保留,发布和自动发布内容的信息。


11
它不会消除保留。它为您添加它们。引用计数仍在进行中,只是自动的。因此,自动参考计数:-D。
mattjgalloway 2012年

6
那么为什么它也没有@autoreleasepool为我添加呢?如果我不控制自动发布或发布的内容(ARC对我来说是自动的),我如何知道何时设置自动发布池?
mk12 2012年

5
但是,您可以控制自动释放池的位置。默认情况下,整个应用中都有一个,但是您可能想要更多。
mattjgalloway 2012年

5
好问题。您只需要“知道”即可。考虑将其添加为类似于为什么可能会用GC语言为垃圾收集器添加提示以继续并立即运行收集周期的原因。也许您知道有很多准备清除的对象,您有一个分配一堆临时对象的循环,所以您“知道”(或Instruments可能会告诉您:),在循环周围添加一个释放池将是好主意。
格雷厄姆·珀克斯

6
循环示例在没有自动释放的情况下也可以正常工作:当变量超出范围时,将释放每个对象。在没有自动释放的情况下运行代码会占用恒定的内存,并显示指针被重用,而在对象的dealloc上放置一个断点则表明,每次调用objc_storeStrong时,在循环中都会对其调用一次。也许OSX在这里做了一些愚蠢的事情,但是在iOS上完全不需要autoreleasepool。
Glenn Maynard 2014年

16

@autoreleasepool不会自动释放任何内容。它创建了一个自动释放池,以便在到达块末尾时,将向在块处于活动状态时由ARC自动释放的任何对象发送释放消息。Apple的《高级内存管理编程指南》对此进行了解释:

在自动释放池块的末尾,向在该块内接收到自动释放消息的对象发送释放消息-每次在该块内向其发送自动释放消息时,对象都会收到一个释放消息。


1
不必要。该对象将收到一条release消息,但是如果保留计数> 1,则不会释放该对象。
andybons

@andybons:已更新;谢谢。这与ARC前的行为有所不同吗?
outis

这是不正确的。不论有无自动释放池,由ARC释放的对象都会在ARC释放后立即发送释放消息。
Glenn Maynard 2014年

7

人们经常误解ARC进行某种垃圾收集或类似操作。事实是,经过一段时间后,Apple的人们(由于llvm和clang项目)意识到在编译时可以完全实现Objective-C的内存管理(所有retainsand和releases等)。这是仅通过阅读代码,甚至在运行之前!:)

为此,只有一个条件:我们必须遵守规则,否则编译器将无法在编译时自动执行该过程。因此,为了确保我们永远不会打破规则,我们不能明确地写releaseretain等这些调用由编译器自动注入到我们的代码。因此,在内部我们还是有autoreleaseS, retainrelease等它只是我们并不需要把它们写下去了。

ARC的A在编译时是自动的,这比垃圾收集等运行时要好得多。

我们仍然有,@autoreleasepool{...}因为拥有它不会违反任何规则,我们可以在需要时随时免费创建/耗尽我们的池:)。


1
ARC是引用计数GC,而不是像JavaScript和Java那样的标记清除GC,但这绝对是垃圾回收。这没有解决问题-“您可以”没有回答“为什么您应该”的问题。你不应该
Glenn Maynard 2014年

3

这是因为您仍然需要向编译器提供有关何时自动释放对象超出范围的安全提示。


您能举例说明何时需要执行此操作吗?
mk12 2012年


因此,例如在ARC之前,我有一个CVDisplayLink在OpenGL应用程序的辅助线程上运行,但是我没有在其runloop中创建自动释放池,因为我知道我没有自动释放任何东西(或使用具有该功能的库)。这是否意味着我现在需要添加,@autoreleasepool因为我不知道ARC是否会决定自动释放某些内容?
mk12 2012年

@ Mk12-否。您仍然始终会有一个自动释放池,每次在主运行循环中都会耗尽它。仅当您要确保已自动释放的对象先被耗尽时才需要添加一个对象,例如,下次运行循环时。
mattjgalloway 2012年

2
@DougW-我研究了编译器的实际功能,并在此处写了博客-iphone.galloway.me.uk/2012/02/a-look-under-arcs-hood- –-episode-3 /。希望能解释编译时和运行时的情况。
mattjgalloway '02

2

引用自https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmAutoreleasePools.html

自动释放池块和线程

Cocoa应用程序中的每个线程都维护自己的自动释放池块堆栈。如果您正在编写仅适用于基金会的程序,或者分离了线程,则需要创建自己的自动释放池块。

如果您的应用程序或线程是长期存在的,并且有可能生成许多自动释放的对象,则应使用自动释放池块(例如在主线程上使用AppKit和UIKit);否则,自动释放的对象会堆积,并且您的内存占用量也会增加。如果您的分离线程未进行Cocoa调用,则无需使用自动释放池块。

注意:如果使用POSIX线程API而不是NSThread创建辅助线程,则除非Cocoa处于多线程模式,否则无法使用Cocoa。仅在分离其第一个NSThread对象之后,可可才进入多线程模式。要在辅助POSIX线程上使用Cocoa,您的应用程序必须首先分离至少一个NSThread对象,该对象可以立即退出。您可以使用NSThread类的isMultiThreaded方法测试Cocoa是否处于多线程模式。

...

在自动引用计数或ARC中,系统使用与MRR相同的引用计数系统,但是会在编译时为您插入适当的内存管理方法调用。强烈建议您将ARC用于新项目。如果使用ARC,尽管在某些情况下可能会有所帮助,但通常无需了解本文档中描述的基础实现。有关ARC的更多信息,请参阅过渡到ARC发行说明。


2

从方法返回新创建的对象时需要自动释放池。例如考虑这段代码:

- (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 DispatchQueueNSOperationQueue,这两个都为您管理一个顶级自动释放池,该池在运行块/任务之前创建,并在完成后销毁。


-4

在这个话题上似乎有很多困惑(至少有80个人现在对此感到困惑,并认为他们需要在代码周围撒上@autoreleasepool)。

如果项目(包括其依赖项)仅使用ARC,则无需使用@autoreleasepool,并且不会做任何有用的事情。ARC将在正确的时间处理释放对象。例如:

@interface Testing: NSObject
+ (void) test;
@end

@implementation Testing
- (void) dealloc { NSLog(@"dealloc"); }

+ (void) test
{
    while(true) NSLog(@"p = %p", [Testing new]);
}
@end

显示:

p = 0x17696f80
dealloc
p = 0x17570a90
dealloc

值超出范围后,将立即释放每个Testing对象,而无需等待退出自动释放池。(NSNumber示例发生了相同的事情;这只是让我们观察到dealloc。) ARC不使用自动释放。

仍允许使用@autoreleasepool的原因是由于尚未完全过渡到ARC的混合ARC和非ARC项目。

如果调用非ARC代码,则它可能返回一个自动释放的对象。在这种情况下,上述循环将泄漏,因为将永远不会退出当前的自动释放池。那就是您想要在代码块周围放置@autoreleasepool的地方。

但是,如果您已经完成了ARC过渡,那么就不用考虑autoreleasepool了。


4
这个答案是错误的,并且也违反了ARC文档。您的证据是轶事,因为您碰巧正在使用编译器决定不自动释放的分配方法。如果为自定义类创建一个新的静态初始化器,则可以很容易地看到这不起作用。创建此初始化程序并在循环中使用它:+ (Testing *) testing { return [Testing new] }。然后,您将看到dealloc直到稍后才被调用。如果将循环的内部包装在一个@autoreleasepool块中,则此问题已修复。
Dima 2014年

@Dima在iOS10上尝试过,在打印对象地址后立即调用dealloc。+ (Testing *) testing { return [Testing new];} + (void) test { while(true) NSLog(@"p = %p", [self testing]);}
KudoCC

@KudoCC-我也是,我也看到了与您相同的行为。但是,当我[UIImage imageWithData]投入方程式时,突然之间,我开始看到传统autorelease行为,要求@autoreleasepool将峰值内存保持在某个合理的水平。
罗布

@Rob我无法帮助自己添加链接
KudoCC
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.