译文 · 原文: Friday Q&A 2011-09-30: Automatic Reference Counting · 作者 Mike Ash
原文:https://www.mikeash.com/pyblog/friday-qa-2011-09-30-automatic-reference-counting.html 发布:2011-09-30 作者:Mike Ash 译者:MiMo(mimo-v2.5-pro);代码块保留英文原样
自苹果宣布自动引用计数(Automatic Reference Counting,ARC)起,便不断有读者请我撰写相关文章。今天终于得以落笔。我将探讨苹果这套新的内存管理系统,解析其运作原理,并分享如何最大化发挥其效能。
概念阐释 Clang 静态分析器是检测代码内存管理错误的利器。若您与我有同感,或许也曾面对分析器的输出暗自思忖:“既然你能识别错误,为何不能直接为我修复呢?”
这实质上正是 ARC 的核心机制。内存管理规则已被内置于编译器之中,但编译器并非仅仅利用这些规则辅助程序员查找错误,而是自动插入必要的调用指令。
ARC(自动引用计数)处于垃圾回收与手动内存管理之间的位置。与垃圾回收相似,ARC 免除了程序员编写 retain(保留)/release(释放)/autorelease(自动释放)调用的负担。但与垃圾回收不同的是,ARC 并不处理保留环(retain cycles)问题 —— 两个相互持有强引用(strong references)的对象,在 ARC 下永远不会被回收,即使没有其他对象引用它们。正因如此,虽然 ARC 让程序员免于应对大多数内存管理问题,但程序员仍需在对象图中避免或手动打破强引用构成的环状结构。
在实现细节方面,ARC 与苹果的垃圾回收实现之间还有另一个关键区别:ARC 并非非此即彼的方案。使用苹果的垃圾回收器时,要么整个应用程序运行在 GC 模式下,要么完全不使用 GC。这意味着应用程序中的所有 Objective-C 代码 —— 包括苹果的所有框架以及你可能引入的所有第三方库 —— 都必须兼容 GC,你才能利用垃圾回收的优势。相比之下,ARC 能够在同一应用程序中与非 ARC 的手动内存管理代码和平共处。这使得我们可以分块转换项目,避免了垃圾回收在首次推出时所遭遇的大规模兼容性和可靠性问题。
XcodeARC 在目前处于测试阶段的 Xcode 4.2 中可用,并且仅当使用 Clang(亦称” Apple LLVM compiler”)编译时才能启用。该设置的名称相当直观,叫做” Objective-C Automatic Reference Counting”(Objective-C 自动引用计数)。启用后即可开始使用。
如果你正在处理现有代码,更改此设置会产生大量错误。ARC 不仅会为你管理内存,还会禁止你尝试自行管理。在 ARC 模式下手动发送 retain/release/autorelease 是非法的。由于普通的非 ARC Cocoa 代码中充斥着这类操作,因此你会遇到很多错误。
幸运的是,Xcode 提供了转换现有代码的工具。选择 Edit -> Refactor… -> Convert to Objective-C ARC…,Xcode 会引导你完成代码转换。尽管有些情况可能需要手动判断,但整个过程基本上是自动的。
基本功能
Cocoa 的内存管理规则相当简单。简而言之:
-
如果你对一个对象执行了 alloc、new、copy 或 retain 操作,就必须通过 release 或 autorelease 来平衡。
-
如果你从上述操作之外的方式获得了一个对象,并且需要它长期存活,就必须 retain 或 copy 它。当然,这之后也必须进行相应的平衡操作。
这些规则非常适合自动化处理。如果你这样写:
Foo *foo = [[Foo alloc] init]; [foo something]; return;编译器可以发现这个不平衡的 alloc。代码因此被转换成:
Foo *foo = [[Foo alloc] init]; [foo something]; [foo release]; return;实际上,编译器并不会插入一条 release 消息发送(message sending),而是插入对一个特殊运行时(runtime)函数的调用:
Foo *foo = [[Foo alloc] init]; [foo something]; objc_release(foo); return;这可以进行一些优化。在 -release 未被重写的常见情况下,objc_release 函数可以绕过 Objective-C 消息发送(message sending),从而获得一定的性能提升。
这种自动化机制可以使代码更安全。大多数 Cocoa 程序员将规则 #2 中的” 长期” 理解为存储在实例变量(instance variables)及类似位置中的对象。我们通常不会对本地的临时对象进行 retain 和 release 操作:
Foo *foo = [self foo]; [foo bar]; [foo baz]; [foo quux];然而,这样做可能有危险:
Foo *foo = [self foo]; [foo bar]; [foo baz]; [self setFoo: newFoo]; [foo quux]; // crash标准的解决办法是让 -foo getter 方法在返回值之前执行一次 retain / autorelease。这样做虽然可行,但会积累大量临时对象,导致内存使用过高。然而,自动引用计数(ARC)会采取过度保守的策略,在此处插入额外的调用:
Foo *foo = objc_retainAutoreleasedReturnValue([self foo]); [foo bar]; [foo baz]; [self setFoo: newFoo]; [foo quux]; // fine objc_release(foo);同样地,即使你编写了一个普通的 getter 方法,ARC(自动引用计数)也会确保其安全性:
- (Foo *)foo { return objc_retainAutoreleaseReturnValue(_foo); }但等等,这根本没有解决过多临时对象的问题!我们在 getter 中仍然执行了 retain/autorelease 序列,而在调用代码中仍然执行了 retain/release 组合。这在效率上仍然相当低下!
别担心。正如我上文提到的,ARC 会发出这些特殊的调用来代替普通的消息发送(message sending),其目的正是为了优化。除了能简单地让 retain 和 release 更快之外,这些调用能够完全消除某些操作。
当 objc_retainAutoreleaseReturnValue 运行时,它会查看栈并从其调用者那里抓取返回地址。这使得它能够精确地预知在它完成后将会发生什么。当编译器优化开启时,对 objc_retainAutoreleaseReturnValue 的调用将受到尾调用优化(tail-call optimization),此时返回地址将指向对 objc_retainAutoreleasedReturnValue 的调用。
通过这种疯狂的返回地址检查机制,runtime(运行时)能够发现自己即将执行某些冗余操作。于是它消除了 autorelease(自动释放)调用,并设置了一个标志来通知调用方移除对应的 retain(保留)操作。整个过程最终在 getter(访问器)中执行单次 retain,在调用代码中执行单次 release(释放),这既保证了完全安全,又提升了效率。
需要注意的是,这项优化与非 ARC(自动引用计数)代码完全兼容。如果 getter 未使用 ARC,该标志就不会被设置,调用方将执行完整的 retain/release 组合。如果 getter 使用了 ARC 但调用方未使用,getter 会发现自己并非返回到直接调用特定 runtime 函数的代码,便会执行完整的 retain/autorelease 组合。虽然会损失一定效率,但正确性得到了保证。
除此之外,ARC 还会自动为所有类创建或填充一个 -dealloc(析构方法)来释放它们的实例变量(instance variables)。你仍然可以手动实现 -dealloc,这对于管理外部资源的类来说是有必要的,但手动释放实例变量已不再是必须(或可能)的事情。ARC 甚至会在末尾自动为你添加 [super dealloc],所以你无需再写。以前,你可能需要这样写:
- (void)dealloc { [ivar1 release]; [ivar2 release]; free(buffer);
[super dealloc]; }现在你只需要这样写:
- (void)dealloc { free(buffer); }如果你的 -dealloc 方法仅仅是释放实例变量,那么完全可以将其整个删除。
循环引用和弱引用
ARC 仍然需要程序员手动解决循环引用,而解决循环引用的最佳方式通常是使用弱引用。
ARC 提供了零弱引用。这类弱引用不仅不会保持被引用对象存活,还会在被引用对象销毁时自动置为 nil。零弱引用能避免悬垂指针以及由此引发的崩溃和莫名行为。
要创建一个零弱引用变量,只需在声明前加上 __weak。例如,这里有一个弱实例变量:
@interface Foo : NSObject { __weak Bar *_weakBar; }同样地,对于本地变量:
__weak Foo *_weakFoo = [object foo];然后你可以像使用其他变量一样使用它,当适当时其值会自动变为零值:
[_weakBar doSomethingIfStillAlive];然而需要注意,__weak 变量可能在任何时刻变为 nil。内存管理本质上是多线程活动,一个被弱引用(weakly referenced)的对象可能在一个线程上被销毁,而另一个线程正在访问它。因此像下面这样的代码是无效的:
if(_weakBar) [self mustNotBeNil: _weakBar];而是将对象存入一个局部强引用中再进行测试:
Bar *bar = _weakBar; if(bar) [self mustNotBeNil: bar];由于 bar 在这里是强引用,对象被保证保持存活(变量非 nil)贯穿这段代码。
ARC(自动引用计数)对归零弱引用(zeroing weak reference)的实现需要 Objective-C 引用计数系统与归零弱引用系统紧密协调。这意味着任何重写了 retain 和 release 的类都不能成为归零弱引用的目标。虽然这种情况不常见,但一些 Cocoa 类(如 NSWindow)会受此限制。幸运的是,如果你遇到这种情况,你会立即知道,因为你的程序会崩溃并显示如下信息:
objc[2478]: cannot form weak reference to instance (0x10360f000) of class NSWindow如果确实需要为这类类创建弱引用,可以使用 __unsafe_unretained 修饰符代替 __weak。这会创建一个非归零的弱引用(non-zeroing weak reference)。必须确保在对象销毁后绝不使用此类指针(最好手动将其置零)。需特别注意,使用非归零弱引用如同玩火。
虽然可以在 Mac OS X 10.6 和 iOS 4 上构建使用 ARC 的程序,但这些系统不支持归零弱引用(zeroing weak references)。此时所有弱引用必须使用 __unsafe_unretained。由于非归零弱引用过于危险,我认为这一限制大大降低了 ARC 在这些较旧系统(译注:指 Mac OS X 10.6 和 iOS 4)上的吸引力。
属性(Properties)
由于属性与内存管理紧密耦合,ARC 自然会在此引入新的行为规范。
ARC 新增了几种所有权修饰符。将属性声明为 strong 会使该属性成为强引用;声明为 weak 则使用归零弱引用;unsafe_unretained 修饰符对应非归零弱引用。当使用 @synthesize 时,编译器会创建相同存储类型的实例变量。
现有的 assign、copy 和 retain 修饰符依然存在,其工作方式与以前保持一致。值得注意的是,assign 现在会创建非归零弱引用(non-zeroing weak reference),因此应尽可能避免使用。
除了新增的修饰符,属性的工作机制一如既往。
Blocks
Blocks(代码块)是 Objective-C 对象,因此同样由 ARC 管理。Blocks 具有特殊的内存管理需求,ARC 会相应处理。Block 字面量必须被复制(copy)而非保留(retain),实际应用中意味着在所有情况下都应优先复制 Blocks 而非保留它们。ARC 遵循此原则。
此外,ARC 知道当 Block 字面量在当前作用域返回后仍被使用时必须复制。非 ARC 代码需要显式复制并自动释放(autorelease)返回的 Blocks:
return [[^{ DoSomethingMagical(); } copy] autorelease];使用 ARC 后,这只需:
return ^{ DoSomethingMagical(); };不过要注意!目前 ARC 不会自动拷贝一个被转换为 id 类型的块字面量。所以这段代码是没问题的:
dispatch_block_t function(void) { return ^{ DoSomethingMagical(); }; }这段代码并非:
id function(void) { return ^{ DoSomethingMagical(); }; }通过简单地复制该代码块很容易绕过这个问题,但需要注意:
return [^{ DoSomethingMagical(); } copy];同样地,当你将块作为 id 参数传递时,需要显式地复制它们:
[myArray addObject: [^{ DoSomethingMagical(); } copy]];幸运的是,这似乎只是因疏忽所致的一个边界案例(edge case),很可能会尽快修复。如果不确定,在额外手动添加一次 copy 操作也不会有问题。
ARC 的另一个重大变化是 __block 限定变量(qualified variables)的行为。__block 修饰符允许代码块(block)修改捕获的变量:
id x; __block id y; void (^block)(void) = ^{ x = [NSString string]; // error y = [NSString string]; // works };在没有 ARC 的情况下,__block 还有一个副作用:当它被代码块(block)捕获时,不会持有其内容的所有权。代码块通常会自动持有(retain)并释放它们捕获的任何对象指针,但 __block 指针被特殊处理,表现得像一个弱引用(weak pointer)。利用这种行为,使用 __block 来避免循环引用(retain cycle)已成为一种常见模式。
在 ARC 下,__block 现在会像其他被捕获的对象指针一样持有其内容。使用 __block 来避免循环引用的代码将不再有效。应改用上文描述的 __weak。
**Toll-Free Bridging(无缝桥接)**ARC 仅适用于 Objective-C 类型。CoreFoundation 类型仍需由程序员手动管理。由于所有权不明确,ARC 禁止在指向 Objective-C 对象的指针与其他类型的指针(包括指向 CoreFoundation 对象的指针)之间进行标准类型转换。以下代码在手动内存管理下相当典型,但在 ARC 中无法编译:
id obj = (id)CFDictionaryGetValue(cfDict, key);为了使代码能够重新编译,你必须通过使用特殊的类型转换注解(casting annotations)来告知 ARC(自动引用计数)所涉及的所有权语义(ownership semantics)。这些注解分别是 __bridge、__bridge_retained 和 __bridge_transfer。
其中最容易理解的是 __bridge。这是一个直接的转换,不涉及任何所有权变更。ARC 接收到这个值后,会像往常一样对其进行管理。这正是我们上面例子中所需要的:
id obj = (__bridge id)CFDictionaryGetValue(cfDict, key);这些其他的类型转换注解会在 ARC 系统间转移所有权。当进行反复桥接时,它们可以帮助减轻痛苦。
这里有一个在返回对象需要释放的情况下使用桥接的示例。
NSString *value = (NSString *)CFPreferencesCopyAppValue(CFSTR("someKey"), CFSTR("com.company.someapp")); [self useValue: value]; [value release];如果我们将这段代码迁移到 ARC(自动引用计数)模式,仅使用 __bridge 转换、移除 release 调用,而不做其他任何更改,最终会导致内存泄漏:
NSString *value = (__bridge NSString *)CFPreferencesCopyAppValue(CFSTR("someKey"), CFSTR("com.company.someapp")); [self useValue: value];这段代码中的 Copy 需要与一个 release 相平衡。ARC 会在初始化 value 时发出一个 retain,然后在 value 不再使用时发出一个与之平衡的 release。由于没有任何操作与最初的 Copy 相平衡,该对象发生了泄漏。
我们可以通过添加一些额外代码来解决这个问题:
CFStringRef valueCF = CFPreferencesCopyAppValue(CFSTR("someKey"), CFSTR("com.company.someapp")); NSString *value = (__bridge NSString *)valueCF; CFRelease(valueCF);
[self useValue: value];不过这样写起来就相当繁琐了。既然无开销桥接(toll-free bridging)的全部意义就是尽可能实现无痛转换,而自动引用计数(ARC)的全部意义就是免除编写内存管理代码的麻烦,那么如果能有更简洁直接的写法就太好了。
__bridge_transfer 注解解决了这个问题。它不仅仅是将一个指针值移入 ARC 的管理范围,还会转移其所有权。当在类型转换中使用 __bridge_transfer 时,它告诉 ARC 这个对象已经被持有过了,ARC 不需要再次 retain 它。由于 ARC 接管了所有权,当它完成时仍然会 release 该对象。最终的结果就是一切都能按预期正常运作:
NSString *value = (__bridge_transfer NSString *)CFPreferencesCopyAppValue(CFSTR("someKey"), CFSTR("com.company.someapp")); [self useValue: value];无缝桥接是双向工作的。和之前一样,ARC(自动引用计数)不允许使用标准类型转换(standard cast)将 Objective-C 对象指针转换为 CoreFoundation 对象指针。这段代码在 ARC 下无法编译:
CFStringRef value = (CFStringRef)[self someString]; UseCFStringValue(value);在类型转换中添加 __bridge 桥接转换(注:用于 ARC 下指针所有权的传递)虽然能使其编译通过,但生成的代码是危险的。
CFStringRef value = (__bridge CFStringRef)[self someString]; UseCFStringValue(value);由于 ARC 不管理 value 的生命周期,它会在对象被传递给 UseCFStringValue 之前就立即释放对该对象的所有权,这可能导致崩溃或其他异常行为。通过使用 __bridge_retained,我们可以指示 ARC 将所有权从该系统中转移出来,交由我们掌控。由于所有权发生了转移,我们现在有责任在使用完该对象后释放它,就像处理任何其他 Core Foundation 代码一样:
CFStringRef value = (__bridge_retained CFStringRef)[self someString]; UseCFStringValue(value); CFRelease(value);这些类型转换注解在无缝桥接之外也很有用。任何时候当你需要将对象指针存储在非 Objective-C 对象管理的存储空间中时,它们都能使操作变得顺畅。在 Cocoa 的各种场景中存在着void *上下文指针,其中一个显著的例子就是工作表(sheets)。在非 ARC 环境下:
NSDictionary *contextDict = [NSDictionary dictionary...]; [NSApp beginSheet: sheetWindow modalForWindow: mainWindow modalDelegate: self didEndSelector: @selector(sheetDidEnd:returnCode:contextInfo:) contextInfo: [contextDict retain]];
- (void)sheetDidEnd: (NSWindow *)sheet returnCode: (NSInteger)code contextInfo: (void *)contextInfo { NSDictionary *contextDict = [(id)contextInfo autorelease]; if(code == NSRunStoppedResponse) ... }与之前一样,这段代码在 ARC(自动引用计数)下会失败,因为对象指针和非对象指针之间不允许进行常规类型转换。然而,通过使用类型转换修饰符,我们不仅能获得 ARC 的允许,还能让 ARC 为我们执行必要的内存管理操作:
NSDictionary *contextDict = [NSDictionary dictionary...]; [NSApp beginSheet: sheetWindow modalForWindow: mainWindow modalDelegate: self didEndSelector: @selector(sheetDidEnd:returnCode:contextInfo:) contextInfo: (__bridge_retained void *)contextDict];
- (void)sheetDidEnd: (NSWindow *)sheet returnCode: (NSInteger)code contextInfo: (void *)contextInfo { NSDictionary *contextDict = (__bridge_transfer NSDictionary *)contextInfo; if(code == NSRunStoppedResponse) ... }总结如下:
__bridge仅在 ARC(自动引用计数)与非 ARC 环境间转移指针,不转移所有权(ownership)。__bridge_transfer将非 Objective-C 指针转换为 Objective-C 指针,同时转移所有权,使 ARC 自动为你释放该对象。__bridge_retained将 Objective-C 指针转换为非 Objective-C 指针,同时转移所有权,因此你(程序员)需负责后续调用CFRelease或通过其他方式释放该对象的所有权。
结构体(Structs)
在 ARC 下,结构体与 Objective-C 对象指针基本无法混用。问题在于编译器无法妥善判断特定结构体在何时被销毁或复制,因而无法在合适的位置插入必要的 retain 和 release 调用。由于此问题极为棘手,且将对象指针置于结构体中本就罕见,ARC 干脆完全放弃了这一场景。若你想将 Objective-C 对象指针放入结构体,必须使用 __unsafe_unretained 修饰它,并承担随之而来的所有问题和风险。
由于在结构体中存放 Objective-C 指针的情况非常少见,因此这可能不会成为你代码中的问题。如果确实遇到了,最佳的解决方案是将该结构体改为一个轻量级的 Objective-C 类。ARC(自动引用计数)会开始为你管理内存,问题也就迎刃而解。
延伸阅读 尽管苹果关于 ARC 的官方文档在 Xcode 4.2 测试版期间仍处于保密状态,但关于该系统的大量信息可以在 Clang 网站上找到,地址是:http://clang.llvm.org/docs/AutomaticReferenceCounting.html
总结 自动引用计数极大地减轻了程序员处理内存管理的负担。ARC 并非一个完整的垃圾回收器。它无法检测保留环(retain cycles),这些需要程序员自行处理和打破。尽管如此,它仍然为编写 Cocoa 代码省去了大量繁琐工作,并且它提供的归零弱引用(zeroing weak references)是处理循环引用的强大工具。
总结
在涉及 CoreFoundation 对象和无缝桥接(toll-free bridging)时,情况会变得更为复杂。ARC(自动引用计数)仅限于处理 Objective-C 部分,因此程序员仍需手动管理 CoreFoundation 一侧的内存。当在 Objective-C 与 CoreFoundation 指针之间进行转换时,需要使用特殊的 __bridge 强制转换修饰符,以告知 ARC 此次转换的内存管理语义。
今天对苹果最新编程语言技术的探讨到此结束。两周后我们将带来另一份精彩的文字大杂烩。届时,请继续仰望星空,并为我们提出主题建议。
Original (English)
Source: https://www.mikeash.com/pyblog/friday-qa-2011-09-30-automatic-reference-counting.html
Since the moment Apple announced it, readers have asked me to write about Automatic Reference Counting, or ARC. Today is the day. I’ll talk about Apple’s new memory management system, how it works, and how to get the most out of it.
ConceptualThe Clang static analyzer is a really useful tool for finding memory management errors in code. If you’re like me, you’ve looked at the output of the analyzer and thought, “If you can spot the error, why can’t you just fix it for me too?”
That, in essence, is what ARC is. The memory management rules are baked into the compiler, but instead of using them to help the programmer find mistakes, it simply inserts the necessary calls on its own.
ARC occupies a middle ground between garbage collection and manual memory management. Like garbage collection, ARC frees the programmer from writing retain/release/autorelease calls. Unlike garbage collection, however, ARC does not deal with retain cycles. Two objects with strong references to each other will never be collected under ARC, even if nothing else refers to them. Because of this, while ARC frees the programmer from dealing with most memory management issues, the programmer still has to avoid or manually break cycles of strong references in the object graph.
When it comes to implementation specifics, there’s another key difference between ARC and Apple’s implementation of garbage collection: ARC is not an either/or proposition. With Apple’s garbage collector, either the entire application runs under GC, or none of it does. This means that all Objective-C code in an application, including all of Apple’s frameworks and all third-party libraries you might include, must be GC compatible for you to take advantage of GC. In contrast, ARC coexists peacefully with non-ARC manual memory managed code in the same application. This makes it possible to convert projects piecemeal without the massive compatibility and reliability problems that garbage collection ran into when it was first introduced.
XcodeARC is available in Xcode 4.2, currently in beta, and only when compiling with Clang (a.k.a. “Apple LLVM compiler”). The setting is called, obviously enough, “Objective-C Automatic Reference Counting”. Turn it on, and off you go.
If you’re working on existing code, changing this setting will produce an enormous quantity of errors. ARC not only manages memory for you, but it forbids you from trying to do it yourself. It’s illegal to manually send retain/release/autorelease when using ARC. Since normal non-ARC Cocoa code is littered with this stuff, you’ll get a lot of errors.
Fortunately, Xcode offers a tool to convert existing code. Select Edit -> Refactor… -> Convert to Objective-C ARC… and Xcode will guide you through converting your code. Although there may be some situations where it needs help figuring out what to do, the process should be largely automatic.
Basic FunctionalityCocoa memory management rules are fairly simple. In short:
-
If you alloc, new, copy, or retain an object, you must balance that with release or autorelease.
-
If you obtain an object from something other than the above, and you need it to stay alive long-term, you must retain or copy it. This must, of course, be balanced later.
These are highly suitable for automation. If you write this:
Foo *foo = [[Foo alloc] init]; [foo something]; return;The compiler can see the unbalanced alloc. The code is thus transformed:
Foo *foo = [[Foo alloc] init]; [foo something]; [foo release]; return;In reality, the compiler does not insert a message send to release. Instead, it inserts a call to a special runtime function:
Foo *foo = [[Foo alloc] init]; [foo something]; objc_release(foo); return;This allows for some optimization. In the common case where -release is not overridden, the objc_release function can bypass Objective-C messaging, resulting in a bit of a speed gain.
This automation can make code safer. Most Cocoa programmers interpret “long-term” in rule #2 to mean objects stored in instance variables and similar places. We don’t normally retain and release local temporary objects:
Foo *foo = [self foo]; [foo bar]; [foo baz]; [foo quux];However, this can be dangerous:
Foo *foo = [self foo]; [foo bar]; [foo baz]; [self setFoo: newFoo]; [foo quux]; // crashThe standard way to work around this is to have the -foo getter do a retain/autorelease before returning the value. This works, but can build up a lot of temporary objects leading to excessive memory usage. ARC, however, will be paranoid and insert extra calls here:
Foo *foo = objc_retainAutoreleasedReturnValue([self foo]); [foo bar]; [foo baz]; [self setFoo: newFoo]; [foo quux]; // fine objc_release(foo);Likewise, even if you write a plain getter, ARC will make it safe as well:
- (Foo *)foo { return objc_retainAutoreleaseReturnValue(_foo); }But wait, this doesn’t solve the problem of excessive temporary objects at all! We’re still doing a retain/autorelease sequence in the getter, and a retain/release combination in the calling code. This is considerably less efficient!
Not to worry. As I mentioned above, ARC emits these special calls instead of plain message sends for the purposes of optimization. In addition to simply making retain and release faster, these calls are able to eliminate certain operations altogether.
When objc_retainAutoreleaseReturnValue runs, it looks on the stack and grabs the return address from its caller. This allows it to see exactly what will happen after it finishes. When compiler optimizations are turned on, the call to objc_retainAutoreleaseReturnValue will be subject to tail-call optimization, and the return address will point to the call to objc_retainAutoreleasedReturnValue.
With this crazy return-address examination, the runtime is able to see that it’s about to perform some redundant work. It therefore eliminates the autorelease, and sets a flag that tells the caller to eliminate its retain. The whole sequence ends up doing a single retain in the getter and a single release in the calling code, which is both completely safe and efficient.
Note that this optimization is fully compatible with non-ARC code. In the event that the getter doesn’t use ARC, the flag won’t be set and the caller will perform a full retain/release combination. In the event that the getter uses ARC but the caller does not, the getter will see that it’s not returning to code that immediately calls the special runtime function, and will perform a full retain/autorelease combination. Some efficiency is lost, but correctness is preserved.
In addition to all of this, ARC also automatically creates or fills out a -dealloc method for all classes to release their instance variables. It’s still possible to manually implement -dealloc, and it’s necessary for classes which manage external resources, but it’s no longer necessary (or possible) to manually release instance variables. ARC will even put the [super dealloc] at the end for you, so you don’t have to. Previously, you might have written this:
- (void)dealloc { [ivar1 release]; [ivar2 release]; free(buffer);
[super dealloc]; }Now you can just write this:
- (void)dealloc { free(buffer); }In the event that your -dealloc method just releases instance variables, it can simply be eliminated altogether.
Cycles and Weak ReferencesARC still requires the programmer to manually resolve reference cycles, and the best way to resolve reference cycles is typically to use weak references.
ARC provides zeroing weak references. These are weak references which not only don’t keep the referenced object alive, but which also automatically become nil when the referenced object is destroyed. Zeroing weak references avoid the potential for dangling pointers and the associated crashes and mysterious behavior.
To make a zeroing weak variable, simply prefix its declaration with __weak. For example, here is a weak instance variable:
@interface Foo : NSObject { __weak Bar *_weakBar; }Likewise for local variables:
__weak Foo *_weakFoo = [object foo];You can then use it like any other variable, with the value automatically becoming nil when appropriate:
[_weakBar doSomethingIfStillAlive];Note, however, that a __weak variable can become nil at almost any time. Memory management is an inherently multithreaded activity, and a weakly referenced object could be destroyed on one thread while another thread is accessing it. Code like this is therefore not valid:
if(_weakBar) [self mustNotBeNil: _weakBar];Instead, store the object into a local strong reference and test that:
Bar *bar = _weakBar; if(bar) [self mustNotBeNil: bar];Because bar is a strong reference here, the object is guaranteed to stay alive (and the variable non-nil) throughout this code.
ARC’s implementation of zeroing weak references requires close coordination between the Objective-C reference counting system and the zeroing weak reference system. This means that any class which overrides retain and release can’t be the target of a zeroing weak reference. While this is uncommon, some Cocoa classes, like NSWindow, suffer from this limitation. Fortunately, if you hit one of these cases, you will know it immediately, as your program will crash with a message like this:
objc[2478]: cannot form weak reference to instance (0x10360f000) of class NSWindowIf you really must make a weak reference to classes such as these, you can use the __unsafe_unretained qualifier in place of __weak. This creates a weak reference which is not zeroing. You must ensure that you never use such a pointer (preferably by zeroing it out manually) after the object it points to has been destroyed. Be careful, as non-zeroing weak references are playing with fire.
While it’s possible to build programs using ARC that run on Mac OS X 10.6 and iOS 4, zeroing weak references are not available on those OSes. All weak references must be __unsafe_unretained here. Because non-zeroing weak references are so dangerous, this limitation significantly decreases the attractiveness of ARC on those OSes in my view.
PropertiesSince properties are so tightly coupled to memory management, it makes sense that ARC would introduce some new behaviors there.
ARC introduces a few new ownership modifiers. Declaring a property as strong makes that property a strong reference. Declaring it as weak uses a zeroing weak reference. The unsafe_unretained modifier uses a non-zeroing weak reference. When @synthesize is used, the compiler creates an instance variable of the same storage type.
The existing modifiers of assign, copy, and retain still exist and work the same way they did before. Notably, assign creates a non-zeroing weak reference, so it should be avoided whenever possible.
Aside from the new modifiers, properties work just as they always have.
BlocksBlocks are Objective-C objects, and as such are also managed by ARC. Blocks have special memory management requirements, and ARC treats them accordingly. Block literals must be copied, not retained, which in practice means that it’s best to copy rather than retain blocks everywhere. ARC follows this practice.
Additionally, ARC knows that block literals must be copied if they’re used after the current scope returns. Non-ARC code needs to explicitly copy and autorelease returned blocks:
return [[^{ DoSomethingMagical(); } copy] autorelease];With ARC, this simply becomes:
return ^{ DoSomethingMagical(); };However, beware! ARC currently does not automatically copy a block literal that’s converted to an id. So while this code is fine:
dispatch_block_t function(void) { return ^{ DoSomethingMagical(); }; }This code is not:
id function(void) { return ^{ DoSomethingMagical(); }; }It’s easy to work around by simply copying the block, but it’s something to be careful with:
return [^{ DoSomethingMagical(); } copy];Likewise, you need to explicitly copy blocks that you pass as id parameters:
[myArray addObject: [^{ DoSomethingMagical(); } copy]];Fortunately, it appears that this is just an edge case that fell through the cracks and is likely to be fixed soon. There’s no problem with throwing in an extra manual copy if you’re unsure.
Another significant change with ARC is the behavior of __block qualified variables. The __block qualifier allows a block to modify a captured variable:
id x; __block id y; void (^block)(void) = ^{ x = [NSString string]; // error y = [NSString string]; // works };Without ARC, __block also has the side effect of not retaining its contents when it’s captured by a block. Blocks will automatically retain and release any object pointers they capture, but __block pointers are special-cased and act as a weak pointer. It’s become a common pattern to rely on this behavior by using __block to avoid retain cycles.
Under ARC, __block now retains its contents just like other captured object pointers. Code that uses __block to avoid retain cycles won’t work anymore. Instead, use __weak as described above.
Toll-Free BridgingARC only works on Objective-C types. CoreFoundation types still have to be managed manually by the programmer. Because there is ambiguity about ownership, ARC forbids standard casts between pointers to Objective-C objects and pointers of other types, including pointers to CoreFoundation objects. The following code, which is fairly typical under manual memory management, does not compile with ARC:
id obj = (id)CFDictionaryGetValue(cfDict, key);In order to make this compile again, you must tell ARC about the ownership semantics involved by using special casting annotations. These annotations are __bridge, __bridge_retained, and __bridge_transfer.
The simplest one to understand is __bridge. This is a direct conversion with no ownership consequences. ARC receives the value and then manages it normally. This is what we want for the above:
id obj = (__bridge id)CFDictionaryGetValue(cfDict, key);The other casting annotations transfer ownership to and from the ARC system. These can help ease the pain when bridging back and forth.
Here’s an example of using bridging in a situation where the returned object needs to be released.
NSString *value = (NSString *)CFPreferencesCopyAppValue(CFSTR("someKey"), CFSTR("com.company.someapp")); [self useValue: value]; [value release];If we move this to ARC by using __bridge, removing the release, and otherwise not making any changes, we end up with a leak:
NSString *value = (__bridge NSString *)CFPreferencesCopyAppValue(CFSTR("someKey"), CFSTR("com.company.someapp")); [self useValue: value];The Copy in this code needs to be balanced with a release. ARC will emit a retain when initializing value, then balance that with a release when value is no longer used. Since nothing balances the original Copy, that object is leaked.
We can work around this with a little extra code:
CFStringRef valueCF = CFPreferencesCopyAppValue(CFSTR("someKey"), CFSTR("com.company.someapp")); NSString *value = (__bridge NSString *)valueCF; CFRelease(valueCF);
[self useValue: value];This is getting fairly verbose, though. Since the whole point of toll-free bridging is to be as painless as possible, and the whole point of ARC is to remove the need to write memory management code, it would be nice if this could be made more straightforward.
The __bridge_transfer annotation solves this problem. Rather than simply move a pointer value into ARC, it moves the value and transfers ownership. When __bridge_transfer is used in a cast, it tells ARC that this object is already retained, and that ARC doesn’t need to retain it again. Since ARC takes ownership, it will still release it when it’s done. The net result is that everything works the way it’s supposed to:
NSString *value = (__bridge_transfer NSString *)CFPreferencesCopyAppValue(CFSTR("someKey"), CFSTR("com.company.someapp")); [self useValue: value];Toll-free bridging works both ways. As before, ARC doesn’t allow a standard cast to convert from an Objective-C object pointer to a CoreFoundation object pointer. This code won’t compile under ARC:
CFStringRef value = (CFStringRef)[self someString]; UseCFStringValue(value);Adding a __bridge to the cast makes it compile, but the resulting code is dangerous:
CFStringRef value = (__bridge CFStringRef)[self someString]; UseCFStringValue(value);Since ARC doesn’t manage the lifetime of value, it will release ownership of the object immediately, before it gets passed to UseCFStringValue, potentially causing a crash or other misbehavior. By using __bridge_retained, we can tell ARC to transfer ownership out of the system and into our hands. Since ownership is transferred, we’re now responsible for releasing the object when done with it, just like with any other CF code:
CFStringRef value = (__bridge_retained CFStringRef)[self someString]; UseCFStringValue(value); CFRelease(value);These cast annotations are useful outside of toll-free bridging as well. Any time you need to store an object pointer in storage that’s not managed as an Objective-C object, they smooth the way. There are void * context pointers found in various places in Cocoa, and a prominent example is sheets. Without ARC:
NSDictionary *contextDict = [NSDictionary dictionary...]; [NSApp beginSheet: sheetWindow modalForWindow: mainWindow modalDelegate: self didEndSelector: @selector(sheetDidEnd:returnCode:contextInfo:) contextInfo: [contextDict retain]];
- (void)sheetDidEnd: (NSWindow *)sheet returnCode: (NSInteger)code contextInfo: (void *)contextInfo { NSDictionary *contextDict = [(id)contextInfo autorelease]; if(code == NSRunStoppedResponse) ... }As before, this fails under ARC, because normal casts between object and non-object pointers are not allowed. However, using the cast modifiers, we can not only get ARC to allow it, but also get ARC to do the requisite memory management for us:
NSDictionary *contextDict = [NSDictionary dictionary...]; [NSApp beginSheet: sheetWindow modalForWindow: mainWindow modalDelegate: self didEndSelector: @selector(sheetDidEnd:returnCode:contextInfo:) contextInfo: (__bridge_retained void *)contextDict];
- (void)sheetDidEnd: (NSWindow *)sheet returnCode: (NSInteger)code contextInfo: (void *)contextInfo { NSDictionary *contextDict = (__bridge_transfer NSDictionary *)contextInfo; if(code == NSRunStoppedResponse) ... }To summarize:
-
__bridge simply transfers a pointer between ARC and non-ARC with no transfer of ownership.
-
__bridge_transfer moves a non-Objective-C pointer to Objective-C and also transfers ownership, such that ARC will release the value for you.
-
__bridge_retained moves an Objective-C pointer to a non-Objective-C pointer and also transfers ownership, such that you, the programmer, are responsible for later calling CFRelease or otherwise releasing ownership of the object.
StructsUnder ARC, structs and Objective-C object pointers pretty much don’t mix. The problem is that there is no good way for the compiler to know when a particular struct is destroyed or copied, and thus no good place for the compiler to insert the necessary retain and release calls. Because it’s such a difficult problem, and because putting object pointers in structs is so unusual anyway, ARC just gives up on the whole proposition. If you want to put an Objective-C object pointer in a struct, you must qualify it with __unsafe_unretained, and deal with all of the problems and danger that this implies.
Because it’s so uncommon to put Objective-C pointers into structs, it’s likely that this won’t be a problem for your code. If it is, your best bet is to change the struct into a lightweight Objective-C class instead. ARC will start managing memory for you and the problem goes away.
Further ReadingWhile Apple’s official documentation on ARC is still under wraps while Xcode 4.2 is in beta, a great deal of information about the system is available on the Clang site here: http://clang.llvm.org/docs/AutomaticReferenceCounting.html
ConclusionAutomatic reference counting substantially reduces the burden on the programmer for dealing with memory management. ARC is not a full garbage collector. It cannot detect retain cycles, and these must be dealt with and broken by the programmer. It still takes a lot of the grunt work out of writing Cocoa code, and the zeroing weak references that it provides are a powerful tool for dealing with cycles.
Things get trickier when it comes to CoreFoundation objects and toll-free bridging. ARC limits itself to dealing with Objective-C, so the programmer still needs to manage the CoreFoundation side manually. When converting between Objective-C and CoreFoundation pointers, the special __bridge cast modifiers need to be used to inform ARC about the memory management semantics of the conversion.
That wraps up today’s exploration of Apple’s latest programming language technology. Come back in two weeks for another fine word salad. Until then, keep watching the skies and sending in your suggestions for topics.