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

@autoreleasepool {
    …
}

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


编辑:简单总结一下我从所有的回答和评论中得到的东西:

新语法:

@autoreleasepool{…}是用于

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

更重要的是:

ARC uses autorelease as well as release. It needs an auto release pool in place to do so. ARC doesn't create the auto release pool for you. However: The main thread of every Cocoa app already has an autorelease pool in it. There are two occasions when you might want to make use of @autoreleasepool: When you are in a secondary thread and there is no auto release pool, you must make your own to prevent leaks, such as myRunLoop(…) { @autoreleasepool { … } return success; }. When you wish to create a more local pool, as @mattjgalloway has shown in his answer.


当前回答

ARC并没有摆脱保留、释放和自动释放,它只是为你添加了所需的。所以仍然有保留的调用,仍然有释放的调用,仍然有自动释放的调用,仍然有自动释放池。

他们对新的Clang 3.0编译器和ARC所做的另一个改变是,他们用@autoreleasepool编译器指令取代了NSAutoReleasePool。NSAutoReleasePool一直是一个特殊的"对象"他们这样做是为了使用它的语法不会与对象混淆所以它通常更简单一点。

所以基本上,你需要@autoreleasepool,因为仍然有自动释放池需要担心。您只是不需要担心添加自动释放调用。

使用自动释放池的示例:

- (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);
            }
        }
    }
}

当然,这是一个非常做作的例子,但是如果你在外部for循环中没有@autoreleasepool,那么你将在以后释放100000000个对象,而不是每次释放10000个对象。

更新: 也可以看到这个答案- https://stackoverflow.com/a/7950636/1068248 -为什么@autoreleasepool与ARC无关。

更新: 我研究了一下这里发生的事情,并把它写在了我的博客上。如果你看一下这里,你会清楚地看到ARC在做什么,以及新样式@autoreleasepool以及它如何引入一个作用域,被编译器用来推断哪些保留、释放和自动释放是必要的。

其他回答

博士TL;

为什么ARC仍然需要@autoreleasepool ?

@autoreleasepool被Objective-C和Swift用来处理内部的autorelese

当你使用纯Swift并分配Swift对象时- ARC处理它

但如果你决定调用/使用Foundation/Legacy Objective-C代码(NSData, Data),其中使用autorelese,则在救援中使用@autoreleasepool

//Swift
let imageData = try! Data(contentsOf: url)

//Data init uses Objective-C code with [NSData dataWithContentsOfURL] which uses `autorelese`

长回答

Mrc arc gc

手动引用计数(MRC)或手动保留-释放(MRR)作为开发人员,您需要对对象上的引用进行手动计数

自动引用计数(ARC)在iOS v5.0和OS X Mountain Lion的xCode v4.2中引入

垃圾收集(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时,对象被系统释放

分配对象 使用引用计数 释放对象。当retainCount == 0时调用deinit

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

释放对象的正确方法:

如果只有这个-悬空指针。因为它仍然可以指向堆中的对象并且可以发送消息 = 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) alloc, new, copy, mutableCopy (+1) retain You are able to own an object as many times as you need(you can call retain several times) (-1) release If you an owner you must release it. If you release more than retainCount it will be 0 (-1) autorelease Adds an object, which should be released, to autorelease pool. This pool will be processed at the end of RunLoop iteration cycle(it means when all tasks will be finished on the stack)[About] and after that release will be applied for all objects in the pool (-1) @autoreleasepool Forces process an autorelease pool at the end of block. It is used when you deal with autorelease in a loop and want to clear resources ASAP. If you don't do it your memory footprint will be constantly increasing

当你在方法调用中分配一个新对象并返回它时,Autorelease被使用

- (B *)foo {
    B *b1 = [[B alloc] init]; //retainCount = 1

    //fix - correct way - add it to fix wrong way
    //[b1 autorelease];

    //wrong way(without fix)
    return b; 
}

- (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最大的优点之一是它可以在编译时自动插入、保留、释放、自动释放,作为开发人员,你不需要再管它了

启用/禁用弧

//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是否开启/关闭

使用预处理器__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生成错误,您应该手动删除保留,释放,自动释放和其他问题

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

新Xcode与MRC

如果你启用了MRC,你会得到下一个错误(警告)(但是构建会成功)

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

人们经常将ARC误解为某种垃圾收集或类似的东西。事实是,经过一段时间后,苹果公司的人(感谢llvm和clang项目)意识到Objective-C的内存管理(所有的保留和释放等)可以在编译时完全自动化。这是通过阅读代码,甚至在它运行之前!:)

为了这样做,只有一个条件:我们必须遵守规则,否则编译器将无法在编译时自动执行该过程。因此,为了确保我们永远不会违反规则,我们不允许明确地写释放、保留等。这些调用由编译器自动注入到代码中。因此在内部我们仍然拥有自动发行,保留,发行等内容。只是我们不需要再写了。

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

我们仍然有@autoreleasepool{…}因为它不违反任何规则,我们可以在任何需要的时候自由地创建/排泄我们的池:)。

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

Autorelease Pool Blocks and Threads Each thread in a Cocoa application maintains its own stack of autorelease pool blocks. If you are writing a Foundation-only program or if you detach a thread, you need to create your own autorelease pool block. If your application or thread is long-lived and potentially generates a lot of autoreleased objects, you should use autorelease pool blocks (like AppKit and UIKit do on the main thread); otherwise, autoreleased objects accumulate and your memory footprint grows. If your detached thread does not make Cocoa calls, you do not need to use an autorelease pool block. Note: If you create secondary threads using the POSIX thread APIs instead of NSThread, you cannot use Cocoa unless Cocoa is in multithreading mode. Cocoa enters multithreading mode only after detaching its first NSThread object. To use Cocoa on secondary POSIX threads, your application must first detach at least one NSThread object, which can immediately exit. You can test whether Cocoa is in multithreading mode with the NSThread class method isMultiThreaded.

...

在自动引用计数(ARC)中,系统使用相同的方法 引用计数系统作为MRR,但它插入适当的内存 管理方法在编译时为您调用。你很坚强 鼓励在新项目中使用ARC。如果你使用ARC,就有 通常不需要理解底层实现 在本文档中描述,尽管在某些情况下可能是 有帮助的。有关ARC的更多信息,请参见过渡到ARC发行说明。

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

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

方法中创建的字符串的保留计数为1。现在谁来平衡保留和释放?

方法本身?不可能,它必须返回创建的对象,所以在返回之前不能释放它。

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

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

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

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

Calling autorelease on an object adds it to the autorelease pool, but what does that really mean, adding an object to the autorelease pool? Well, it means telling your system "I want you to to release that object for me but at some later time, not now; it has a retain count that needs to be balanced by a release otherwise memory will leak but I cannot do that myself right now, as I need the object to stay alive beyond my current scope and my caller won't do it for me either, it has no knowledge that this needs to be done. So add it to your pool and once you clean up that pool, also clean up my object for me."

With ARC the compiler decides for you when to retain an object, when to release an object and when to add it to an autorelease pool but it still requires the presence of autorelease pools to be able to return newly created objects from methods without leaking memory. Apple has just made some nifty optimizations to the generated code which will sometimes eliminate autorelease pools during runtime. These optimizations require that both, the caller and the callee are using ARC (remember mixing ARC and non-ARC is legal and also officially supported) and if that is actually the case can only be known at runtime.

考虑以下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];

正如你所看到的,苹果消除了aturelease,因此当池被破坏时,也延迟了对象释放,以及安全保留。要了解更多关于这是如何实现的,以及幕后到底发生了什么,请查看这篇博客文章。

现在来看看真正的问题:为什么要使用@autoreleasepool?

对于大多数开发人员来说,现在在他们的代码中使用这个结构的原因只有一个,那就是在适用的地方保持较小的内存占用。例如,考虑这个循环:

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

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

如果你这样写代码:

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

然后,每次for循环运行时都会创建一个新池,并在每次循环迭代结束时销毁。这样,尽管循环运行了一百万次,但在任何时候最多只有一个临时对象挂在内存中。

在过去,当你管理线程(例如使用NSThread)时,你经常不得不自己管理自动释放池,因为只有主线程自动有一个Cocoa/UIKit应用程序的自动释放池。然而,这几乎是今天的遗留问题,因为今天你可能不会使用线程开始。你可以使用GCD的DispatchQueue或NSOperationQueue,这两个都为你管理一个顶级的自动释放池,在运行块/任务之前创建,一旦完成就销毁。

这是因为您仍然需要向编译器提供提示,说明何时自动释放对象超出作用域是安全的。