首页 文章

为什么ARC仍然需要@autoreleasepool?

提问于
浏览
185

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

@autoreleasepool {
    …
}

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


EDIT: 总结我从所有的答案和评论中得到的简洁:

New Syntax:

@autoreleasepool { … } 是新的语法

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

More importantly:

  • ARC使用 autorelease 以及 release .

  • 它需要一个自动释放池才能这样做 .

  • ARC不会为您创建自动释放池 . 然而:

  • 每个Cocoa应用程序的主线程中都有一个自动释放池 .

  • 有两种情况可能需要使用 @autoreleasepool

  • 当您处于辅助线程且没有自动释放池时,您必须自己制作以防止泄漏,例如 myRunLoop(…) { @autoreleasepool { … } return success; } .

  • 当你想创建一个更本地的游泳池时,正如@mattjgalloway在他的回答中所示 .

7 回答

  • 205

    ARC没有摆脱保留,发布和自动释放,它只是为您添加所需的 . 所以仍有调用保留,仍有调用释放,仍有调用自动释放,仍有自动释放池 .

    他们使用新的Clang 3.0编译器和ARC进行的其他一项更改是,他们用 @autoreleasepool 编译器指令替换了 NSAutoReleasePool . NSAutoReleasePool 无论如何总是有点特殊"object"并且它们使得使用它的语法不与对象混淆,因此它通常更简单一些 .

    所以基本上,你需要 @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 -loop中,那么你将在以后释放100000000个对象,而不是每次围绕外部 for -loop释放10000个 .

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

    Update: 我看了一下这里发生了什么的内部和wrote it up on my blog . 如果您查看那里,那么您将看到ARC正在做什么,以及编译器如何使用新样式 @autoreleasepool 以及它如何引入范围来推断需要保留,释放和自动释放的信息 .

  • -4

    @autoreleasepool doesn 't autorelease anything. It creates an autorelease pool, so that when the end of block is reached, any objects that were autoreleased by ARC while the block was active will be sent release messages. Apple' s Advanced Memory Management Programming Guide解释如此:

    在自动释放池块的末尾,在块中接收到自动释放消息的对象被发送释放消息 - 对象在每次在块内发送自动释放消息时接收释放消息 .

  • 0

    人们经常误解ARC进行某种垃圾收集等 . 事实是,过了一段时间,Apple的人们(感谢llvm和clang项目)意识到Objective-C的内存管理(所有 retainsreleases 等)可以在编译时完全自动化 . 这就是通过阅读代码,甚至在它运行之前! :)

    为了做到这一点,只有一个条件:我们必须遵循rules,否则编译器将无法在编译时自动化该过程 . 因此,为了确保我们违反规则,我们不允许显式写入 releaseretain 等 . 这些调用由编译器自动注入到我们的代码中 . 因此在内部我们仍然有 autorelease s, retainrelease 等 . 我们不需要再写它们了 .

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

    我们仍然有 @autoreleasepool{...} 因为它没有违反任何规则,我们可以随时创建/消耗我们的池我们需要它:) .

  • 3

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

  • 7

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

    自动释放池块和线程Cocoa应用程序中的每个线程都维护自己的自动释放池块堆栈 . 如果您正在编写仅基于Foundation的程序或者分离线程,则需要创建自己的自动释放池块 . 如果您的应用程序或线程是长期存在的,并且可能会产生大量自动释放对象,你应该使用自动释放池块(如主线程上的AppKit和UIKit做);否则,自动释放的对象会累积,并且您的内存占用会增加 . 如果您的分离线程没有进行Cocoa调用,则不需要使用自动释放池块 . 注意:如果使用POSIX线程API而不是NSThread创建辅助线程,则除非Cocoa处于多线程模式,否则不能使用Cocoa . Cocoa仅在分离其第一个NSThread对象后才进入多线程模式 . 要在辅助POSIX线程上使用Cocoa,您的应用程序必须首先分离至少一个NSThread对象,该对象可以立即退出 . 您可以使用NSThread类方法isMultiThreaded测试Cocoa是否处于多线程模式 .

    ...

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

  • 1

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

    - (NSString *)messageOfTheDay {
        return [[NSString alloc] initWithFormat:@"Hello %@!", self.username];
    }
    

    方法中创建的字符串的保留计数为1 . 现在谁来 balancer 保留计数与释放?

    方法本身?不可能,它必须返回创建的对象,因此在返回之前不得释放它 .

    方法的调用者?调用者不希望检索需要释放的对象,方法名称并不意味着创建了一个新对象,它只表示返回一个对象,而这个返回的对象可能是一个需要释放的新对象,但它可能是很好是现有的没有 . 该方法返回的内容甚至可能取决于某些内部状态,因此调用者无法知道是否必须释放该对象并且它不应该关心 .

    如果调用者必须总是按照约定释放所有返回的对象,那么在从方法返回之前,总是必须保留每个新创建的对象,并且一旦调用者超出范围就必须释放它,除非它又被退回了 . 在许多情况下,这将是非常低效的,因为如果调用者不总是释放返回的对象,则在许多情况下可以完全避免改变保留计数 .

    这就是为什么有自动释放池,所以第一种方法实际上会成为

    - (NSString *)messageOfTheDay {
        NSString * res = [[NSString alloc] initWithFormat:@"Hello %@!", self.username];
        return [res autorelease];
    }
    

    在一个对象上调用 autorelease 会将它添加到自动释放池中,但是这对于自动释放池添加一个对象到底意味着什么呢?嗯,这意味着告诉你的系统“我希望你为我释放那个对象但是在以后的某个时间,而不是现在;它有一个需要通过释放来 balancer 的保留计数,否则内存会泄漏但是我不能自己做现在,因为我需要对象保持活动超出我当前的范围,我的调用者也不会为我做这件事,它不知道这需要做 . 所以把它添加到你的池中,一旦你清理了游泳池,也为我清理我的物体 . “

    使用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,因此也消除了池被破坏时延迟的对象释放,以及安全保留 . 要了解更多关于可能性和真实性的信息在幕后进行,check out this blog post.

    现在回答实际问题:为什么要使用 @autoreleasepool

    对于大多数开发人员来说,今天只有一个原因是在他们的代码中使用这个构造,并且在适用的情况下保持较小的内存占用 . 例如 . 考虑这个循环:

    for (int i = 0; i < 1000000; i++) {
        // ... code ...
        TempObject * to = [TempObject tempObjectForData:...];
        // ... do something with to ...
    }
    

    假设每次调用 tempObjectForData 都可以创建一个返回自动释放的新 TempObject . for循环将创建一百万个这些临时对象,这些临时对象都在当前自动释放池中收集,并且只有在该池被销毁时,所有临时对象也会被销毁 . 在此之前,您在内存中有一百万个这些临时对象 .

    如果您编写这样的代码:

    for (int i = 0; i < 1000000; i++) @autoreleasepool {
        // ... code ...
        TempObject * to = [TempObject tempObjectForData:...];
        // ... do something with to ...
    }
    

    然后每次for循环运行时都会创建一个新池,并在每次循环迭代结束时销毁 . 这样,尽管循环运行了一百万次,但最多一个临时对象随时都会在内存中闲置 .

    在过去,您经常必须在管理线程时自己管理自动释放池(例如使用 NSThread ),因为只有主线程自动拥有Cocoa / UIKit应用程序的自动释放池 . 然而今天这几乎是遗产,因为今天你可能不会使用GCD DispatchQueueNSOperationQueue ,这两个都会为你管理一个顶级自动释放池,在运行块/任务之前创建并在完成后销毁 .

  • 13

    关于这个话题似乎有很多混淆(至少有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代码,它可能会返回一个自动释放的对象 . 在这种情况下,上面的循环会泄漏,因为永远不会退出当前的自动释放池 . 那个's where you'd想在代码块周围放一个@autoreleasepool .

    但如果你完全完成了ARC转换,那么就忘掉autoreleasepool了 .

相关问题