为什么ARC仍然需要@autoreleasepool ?

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

@autoreleasepool {
…
}

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


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

新语法:

@autoreleasepool { … }是新的语法

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

更重要的是:

  • ARC使用autoreleaserelease
  • 它需要一个自动释放池来实现这一点。
  • ARC不会为您创建自动释放池。然而< em >:
    • 每个Cocoa应用程序的主线程都有一个自动释放池。
    • 李< / ul > < / > 有两种情况下你可能想要使用@autoreleasepool:
      1. 当你在次要线程中并且没有自动释放池时,你必须创建自己的释放池以防止泄漏,例如myRunLoop(…) { @autoreleasepool { … } return success; }
      2. 当您希望创建一个更本地的池时,如@mattjgalloway在他的回答中所示。
      3. 李< / ol > < / >
71965 次浏览

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

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

他们对新的Clang 3.0编译器和ARC所做的另一个变化是,他们用@autoreleasepool编译器指令取代了NSAutoReleasePool。不管怎样,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);
}
}
}
}

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

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

< >强更新: 我看了一下这里发生的事情和写在我的博客上了的内部结构。如果你看一下那里,你会清楚地看到ARC在做什么,以及新样式@autoreleasepool和它如何引入一个作用域被编译器用来推断什么保留,释放&

@autoreleasepool不会自动释放任何东西。它创建了一个自动释放池,这样当到达块的末尾时,任何在块处于活动状态时被ARC自动释放的对象都将被发送释放消息。苹果的高级内存管理编程指南是这样解释的:

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

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

为了这样做,只有一个条件:我们必须遵循规则,否则编译器将无法在编译时自动执行该过程。因此,为了确保从来没有违反规则,不允许显式地编写releaseretain等。这些调用由编译器自动注入到代码中。因此在内部我们仍然有__abc2, retainrelease等。只是我们不需要再写了。

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

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

关于这个主题似乎有很多困惑(至少有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

一旦值超出范围,每个测试对象就会被释放,而不需要等待自动释放池被退出。(同样的事情发生在NSNumber的例子中;这只是让我们观察dealloc.) ARC不使用自动释放。

@autoreleasepool仍然被允许的原因是对于混合的ARC和非ARC项目,这些项目还没有完全过渡到ARC。

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

但是如果你已经完全完成了ARC转换,那么忘记autoreleasepool吧。

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

自动释放池块和线程

Cocoa应用程序中的每个线程都维护自己的堆栈 自动释放池块。如果您正在编写仅限foundation的程序 或者如果你分离一个线程,你需要创建你自己的自动释放 池块。< / p >

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

.自动释放池块

注意:如果您使用POSIX线程api创建辅助线程 而不是NSThread,你不能使用Cocoa,除非Cocoa在 多线程模式。Cocoa只在 分离它的第一个NSThread对象。在辅助POSIX上使用Cocoa 线程,你的应用程序必须首先分离至少一个NSThread 对象,该对象可以立即退出。您可以测试Cocoa是否在 使用NSThread类方法的多线程模式是multithreading .

...

在自动引用计数(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];
}

在对象上调用autorelease将其添加到自动释放池中,但将对象添加到自动释放池中到底是什么意思呢?这意味着告诉你的系统"我想让你为我释放那个对象但以后再释放,不是现在;它有一个保留计数,需要通过释放来平衡,否则内存会泄漏,但我现在不能自己这样做,因为我需要对象在我当前的作用域之外保持活跃,而我的调用者也不会为我这样做,它不知道这需要做。把它添加到池中一旦你清理了池,也为我清理了我的对象。"

使用ARC,编译器为你决定什么时候保留一个对象,什么时候释放一个对象,什么时候将它添加到自动释放池,但它仍然需要自动释放池的存在,以能够从方法中返回新创建的对象,而不会泄漏内存。苹果刚刚对生成的代码做了一些漂亮的优化,有时会在运行时消除自动释放池。这些优化要求调用方和被调用方都在使用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];

正如你所看到的,苹果消除了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's或NSOperationQueue's,这两个都为你管理顶级的自动释放池,在运行块/任务之前创建,并在完成后销毁。

博士TL;

为什么ARC仍然需要@autoreleasepool ?

@autoreleasepool被Objective-C和Swift用来与里面的autorelese一起工作

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

但是如果你决定调用/使用Foundation/Legacy Objective-C code(NSDataData),它在内部使用autorelese,那么在救援中使用@autoreleasepool

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


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

长回答

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时,对象被释放由系统

  • 分配对象
  • 使用引用计数
  • 释放对象。当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

释放对象的正确方法:

  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) __abc0, __abc1, __abc2, __abc3
  • (+1) retain你可以任意多次拥有一个对象(你可以多次调用retain)
  • release如果你是所有者,你必须释放它。如果你释放的数量超过了retainCount,它将为0
  • (-1) autoreleaseautorelease pool中添加一个应该被释放的对象。该池将在RunLoop迭代周期的结束(这意味着当堆栈上的所有任务都将完成时)<一口>[对]< /一口>处处理,之后将对池中的所有对象应用release
  • (-1) @autoreleasepool强制在块结束处处理一个自动释放池。当你在循环中处理autorelease并希望尽快清除资源时使用它。如果你不这样做,你的内存占用将不断增加

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最大的优点之一是它会自动在编译时中插入retainreleaseautorelease,作为开发人员,你不应该再关心它了

启用/禁用弧

//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...

新Xcode与MRC

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

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