为什么ARC仍然需要@autoreleasepool?

时间:2012-01-31 21:03:09

标签: objective-c memory-management xcode4.2 automatic-ref-counting

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

@autoreleasepool {
    …
}

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


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

新语法:

@autoreleasepool { … }

的新语法
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
…
[pool drain];

更重要的是:

  • ARC使用autorelease以及release
  • 它需要一个自动释放池才能这样做。
  • ARC不会为您创建自动发布池。 然而:
    • 每个Cocoa应用程序的主线程中都有一个自动释放池。
  • 您可能有两次想要使用@autoreleasepool
    1. 如果您处于辅助线程并且没有自动释放池,则必须自行防止泄漏,例如myRunLoop(…) { @autoreleasepool { … } return success; }
    2. 当你想创建一个更本地的游泳池时,正如@mattjgalloway在他的回答中所示。

8 个答案:

答案 0 :(得分:208)

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无关。

<强>更新 我看了一下这里发生了什么的内部结构和wrote it up on my blog。如果您查看那里,那么您将看到ARC正在做什么以及新样式@autoreleasepool以及它如何引入范围如何被编译器用于推断有关保留,发布和保留的信息。自动释放是必需的。

答案 1 :(得分:14)

@autoreleasepool不会自动释放任何内容。它创建一个自动释放池,以便在到达块结束时,在块处于活动状态时由ARC自动释放的任何对象将被发送释放消息。 Apple的Advanced Memory Management Programming Guide解释了这一点:

  

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

答案 2 :(得分:7)

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

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

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

我们仍然有@autoreleasepool{...}因为它没有违反任何规则,我们可以随时创建/排空我们的游泳池:)。

答案 3 :(得分:3)

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

答案 4 :(得分:1)

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

  

自动释放池块和线程

     

Cocoa应用程序中的每个线程都维护着自己的堆栈   自动释放池块。如果您正在编写仅限基金会的计划   或者如果你分离一个线程,你需要创建自己的自动释放   泳池区。

     

如果您的应用程序或线程长寿并可能生成   很多自动释放的对象,你应该使用自动释放池块   (就像AppKit和UIKit在主线程上做的那样);否则,自动释放   对象累积,内存占用增长。如果你的超然   线程不会使Cocoa调用,您不需要使用   自动释放池块。

     

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

...

  

在自动参考计数或ARC中,系统使用相同的参考计数   引用计数系统作为MRR,但它插入了适当的内存   管理方法在编译时调用。你是强烈的   鼓励将ARC用于新项目。如果您使用ARC,则有   通常无需了解底层实现   虽然在某些情况下可能会在本文档中描述   很有帮助。有关ARC的更多信息,请参阅转换为ARC发行说明。

答案 5 :(得分:0)

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

- (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,从而消除了销毁池时延迟释放的对象以及安全性。要了解有关如何实现以及幕后实际情况的更多信息,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循环将创建这些临时对象中的一百万个,这些临时对象全部收集在当前的autoreleasepool中,并且只有在销毁该池后,所有临时对象也会被销毁。在此之前,您的内存中只有一百万个临时对象。

如果您改写这样的代码:

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

然后,每次for循环运行时都会创建一个新池,并在每次循环迭代结束时将其销毁。这样一来,尽管循环运行了100万次,但随时最多有一个临时对象在内存中徘徊。

过去,在管理线程时(例如,使用NSThread),您通常还必须自己管理自动释放池,因为只有主线程自动具有Cocoa / UIKit应用程序的自动释放池。但是,今天这几乎已经成为历史,因为今天您可能不会开始使用线程。您将使用DispatchQueueNSOperationQueue的GCD,这两个都可以为您管理一个顶级自动释放池,该池是在运行块/任务之前创建的,并在完成后销毁。 >

答案 6 :(得分:0)

TL;DR

<块引用>

为什么 ARC 还需要@autoreleasepool?

@autoreleasepool 被 Objective-C 和 Swift 用于处理使用 autorelese 的 MRC Objective-C 代码,例如 NSDataData

长答案

MRC、ARC、GC

Manual Reference Counting(MRC)Manual Retain-Release(MRR) 作为开发人员,您有责任手动计算对象上的引用

Automatic Reference Counting(ARC) 是在 iOS v5.0 和 OS X Mountain Lion 和 xCode v4.2 中引入的

Garbage Collection(GC) 可用于 Mac OS,但在 OS X Mountain Lion 中已弃用。必须转移到 ARC

MRC 和 ARC 中的引用计数

//MRC
NSLog(@"Retain Count: %d", [variable retainCount]);

//ARC
NSLog(@"Retain Count: %ld", CFGetRetainCount((__bridge CFTypeRef) variable));

堆中的每个对象都有一个整数值,表示在它上面指出了多少引用。当它等于 0 对象被释放系统

  • 分配对象
  • 使用引用计数
  • 释放对象。 deinitretainCount == 0
  • 时被调用

MRC

A *a1 = [[A alloc] init]; //this A object retainCount = 1
    
A *a2 = a1;
[a2 retain]; //this A object retainCount = 2

// a1, a2 -> object in heap with retainCount

释放对象的正确方法:

  1. release 如果只有这个 - 悬空指针。因为它仍然可以指向堆中的对象并且可以发送消息
  2. = nil 如果只是这样 - 内存泄漏。 deinit 不会被调用
A *a = [[A alloc] init]; //++retainCount = 1
[a release]; //--retainCount = 0
a = nil; //guarantees that even somebody else has a reference to the object, and we try to send some message thought variable `a` this message will be just skipped

使用引用计数(对象所有者规则):

-(0 -> 1) allocnewcopymutableCopy -(+1) retain 您可以根据需要多次拥有一个对象(您可以多次调用 retain) -(-1) release。如果您是所有者,则必须将其释放。如果您释放的数量超过 retainCount,它将为 0 -(-1) autorelease。添加一个应该被释放到 autorelease pool 的对象。该池将在RunLoop 迭代周期结束(这意味着当所有任务都在堆栈上完成时)进行处理,之后release 将应用于池中的所有对象 -(-1) @autoreleasepool。强制在块结束处理自动释放池。当您在循环中处理 autorelease 并希望尽快清除资源时使用它。如果你不这样做,你的内存占用会不断增加

autorelease 用于在方法调用中分配新对象并返回它

- (B *)foo {
//    NSString *label = [[NSString alloc] initWithString:@"Hello World"];
    B *b1 = [[B alloc] init]; //retainCount = 1

    //wrong way to run testFoo()
    return b; 
    
    //correct way
    //[b1 autorelease];
}

- (void)testFoo {
    B *b2 = [a foo];
    [b2 retain]; //retainCount = 2
    //some logic
    [b2 release]; //retainCount = 1
    
    //Memory Leak
}

@autoreleasepool

- (void)testFoo {
    for(i=0; i<100; i++) {
        B *b2 = [a foo];
        //process b2
    }
}

ARC

ARC 的最大优点之一是它会在 编译时间 中自动插入 retainreleaseautorelease 并作为开发人员你不应该再照顾它了

启用/禁用 ARC

//enable
-fobjc-arc
//disable
-fno-objc-arc

优先级从高到低的变体

//1. local file - most priority
Build Phases -> Compile Sources -> Compiler Flags(Select files -> Enter) 

//2. global
Build Settings -> Other C Flags(OTHER_CFLAGS)

//3. global
Build Settings -> Objective-C Automatic Reference Counting(CLANG_ENABLE_OBJC_ARC)

检查 ARC 是否启用/禁用

Preprocessor __has_feature 函数被使用

__has_feature(objc_arc)

编译时间

// error if ARC is Off. Force to enable ARC
#if  ! __has_feature(objc_arc)
    #error Please enable ARC for this file
#endif

//or

// error if ARC is On. Force to disable ARC
#if  __has_feature(objc_arc)
    #error Please disable ARC for this file
#endif

运行时

#if __has_feature(objc_arc)
    // ARC is On
    NSLog(@"ARC on");
#else
    // ARC is Off
    NSLog(@"ARC off");
#endif

逆向工程(针对 Objective-C)

//ARC is enabled
otool -I -v <binary_path> | grep "<mrc_message>"
//e.g.
otool -I -v "/Users/alex/ARC_experiments.app/ARC_experiments"  | grep "_objc_release"

//result
0x00000001000080e0   748 _objc_release

//<mrc_message>
_objc_retain
_objc_release
_objc_autoreleaseReturnValue
_objc_retainAutoreleaseReturnValue
_objc_retainAutoreleasedReturnValue
_objc_storeStrong

将 Objective-C MRC 迁移到 ARC 的工具

ARC 生成错误,您应该手动删除 retainreleaseautorelease 和其他问题

Edit -> Convert -> To Objective-C ARC...

带有 MRC 的新 Xcode

如果您启用 MRC,您会收到下一个错误(警告)(但构建会成功)

//release/retain/autorelease/retainCount
'release' is unavailable: not available in automatic reference counting mode
ARC forbids explicit message send of 'release'

答案 7 :(得分:-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。