理解 Objective-C 的 ARC

英文原文:Understanding Automatic Reference Counting in Objective-Chtml

自动引用计数(Automatic Reference Counting, ARC)把压在程序员们肩头的管理内存的重担卸除了很多,更不用说让跟踪内存泄漏那样的烦心事也少了不少。不过,虽然ARC很棒,咱们仍然不能彻底把内存管理这回事儿抛在脑后。 ios

这篇文章将要讨论如下方面的问题,帮助你们快速进入ARC的世界。 git

  1. 内存的引用计数: 快速复习
  2. ARC的工做原理
  3. 在工程中开启ARC
  4. ARC施加的新规则
  5. ARC限定符 - 声明的属性
  6. ARC限定符 - 常规变量
  7. 移植到ARC
  8. 引入不兼容ARC的代码
  9. 我该用ARC吗?
 

发生了什么事? 程序员

ARC Related changes to Xcode 4.2

在ARC出现之前,程序员们只能靠retain/relese/autorelease来确保对象们刚好“坚持”到被须要的那一刻。若是忘了retain,或者屡次release某个对象,程序就会发生内存泄漏的问题,甚至直接崩溃。 github

在Xcode 4.2中,除了语法检查外,Apple的新LLVM编译器还将内存管理的苦差事接了过来,它会检查代码,决定什么时候释放对象。Apple的文档里是这么定义ARC的: xcode

“自动引用计数(ARC)是一个编译器级的功能,它能简化Cocoa应用中对象生命周期管理(内存管理)的流程。” 

ARC使内存管理在大部分时候变得如同小事一桩,但咱们仍要在决定本身的类如何管理其它对象的引用时承担一些责任。 安全

那么,让咱们正式开始吧…… session


 

引用计数: 快速复习 

手工管理、引用计数式的内存管理在iOS中是这样工做的: 当使用alloc/init(或其它相似方法)建立对象时,随同对象返回的,还有个retainCount,其值为1,代表咱们得到了这个对象的全部权。 app

?
1
2
3
NSObject *obj = [[NSObject alloc] init];
// do some stuff
[obj release];
在对象的alloc/init和release(即放弃对象的全部权)之间,咱们能够任意处理它,这很安全,由于系统是没法回收正在使用中的对象的。 

将对象加入到自动释放池也是相似,对象会一直存在,直到将来的某个时间咱们再也不须要它,才会被系统回收。 ide

?
1
2
3
4
-(NSObject*) someMethod {
   NSObject *obj = [[[NSObject alloc] init] autorelease];
   return obj;  // will be deallocated by autorelease pool later
}

 

ARC的工做原理 

大多数新的iOS程序员都会在引用计数这问题上遇到理解障碍。ARC则是一个编译前的步骤,它为咱们的代码自动加上retain/release/autorelease语句。 

ARC并非垃圾收集,并且,引用计数也没有消失,只是变成自动而已。听起来像是过后追加的这么一个功能,不过,只要咱们想想Objective-C有多少功能是经过对源文件的预处理来实现的,就不会这么想了。 

当采用ARC后,代码只要这样写: 

?
1
2
NSObject *obj = [[NSObject alloc] init];
// do some stuff
ARC会自动将它变成: 
?
1
2
3
NSObject *obj = [[NSObject alloc] init];
// do some stuff
[obj release];  // **Added by ARC**

从下图(来自Apple官方文档)看起来,好像retain/release的数量快遇上真正有用的代码了。固然,这确定不是熟手的状况,不过能够当作是对新手的保守估计。这些代码跑起来,要跟踪某个内存问题真的是会搞死人。 

来源: Programming With ARC Release Notes


 

在工程中开启ARC

若是想开启ARC,只要在工程的Build Settings中设置ARC为YES。在幕后,其实是设置了-fobj-arc的编译器标识。

ARC施加的新规则

若是想用ARC,必须服从一些新规则。

1. 对象的Alloc/Init

建立对象的方法跟之前同样,但你必定不能调用retain/release/autorelease/retainCount。也不能经过selector偷偷地调用它们: 禁止使用@selector(retain)和@selector(release)。


 

2. dealloc方法 

ARC为自动为你调用,必定不能直接调用dealloc。不过,若是你须要释放实例变量之外的资源,仍是能够建立自定义的dealloc方法。但在这个方法里,不要调用[super dealloc]。由于ARC会帮你调。 

3. 声明的属性 

在ARC以前,咱们是用@property指令中的assign/retain/copy参数来告诉编译器,如何管理这些属性的内存。用了ARC以后,这些参数就做废了,改用weak/strong这两个参数。 


 

4. C结构中的对象指针 

一样禁止使用。文档里建议不要把它们放在结构了,改放到类里去。不然ARC就不认识它们了。可能会出现一些移植上的问题。不过,ARC是能够以文件为单位来关闭的。参考下文的“引入不兼容ARC的代码”。 

5. id与void*之间的临时互转 

当咱们在Core Foundation的C函数和Foundation Kit的Objective-C方法间传递对象时,经常须要进行id和void*两个类型的互转。叫作免费桥接(Toll Free Bridging)。 

若是使用ARC,必须在CF对象进入和脱离ARC的控制时,用提示/限定符来告知编译器。限定符有__bridge、__bridge_retain和__bridge_transfer。另外,仍须要用CFRetain和CFRelease来管理Core Foundation的对象。 

这一块已经比较高深了,若是你不清楚CF对象是什么,也不须要太烦恼。 

 

6. 以@autoreleasepool代替NSAutoReleasePool 

兼容ARC的代码不能再使用NSAutoReleasePool对象,而要改用@autoreleasepool{}块。一个很好的例子: 

?
1
2
3
4
5
6
int main( int argc,  char *argv[])
{
   @autoreleasepool {
     return UIApplicationMain(argc, argv, nil, NSStringFromClass([ExampleAppDelegate  class ]));
   }
}

7. 其它 

基于Zone的内存已经没了(在运行时里也没了)。不能再使用NSAllocateObject和NSDeallocateObject。 


 

ARC限定符 - 声明的属性 

身为程序员,习惯于作出一些决定,例如把某个量声明为变量仍是常量、本地仍是全局,等等。所以,在这里,咱们也要决定某个属性与其它属性的关系。咱们用strong/weak来把这一关系告诉编译器。 

强引用 

强引用是对某对象的引用,而且能阻止它被回收。换句话说,强引用建立了一个全部关系。在ARC以前,咱们这么写: 

?
1
2
// Non-ARC Compliant Declaration
@property(retain) NSObject *obj;

在ARC下,咱们须要这么写,以确保当前实例得到被引用对象的全部权(主人不被回收,它也不能被回收)。 

?
1
2
// ARC Compliant Declaration
@property(strong) NSObject *obj;

弱引用 

弱引用是对某对象的引用,但不能阻止它被回收。换句话说,弱引用并不会建立全部关系。在ARC以前,咱们这么写:

 

?
1
2
// Non-ARC Compliant Declaration
@property(assign) NSObject *parentObj;
在ARC下,咱们须要这么写,以确保当前实例没有得到被引用对象的全部权(通常来讲,子对象不该该拥有父对象,这时能够用弱引用)。 
?
1
2
// ARC Compliant Declaration
@property(weak) NSObject *parentObj;

 

ARC限定符 - 常规变量

上一节是说明如何管理属性。对于常规变量,则有:

 

?
1
2
3
4
__strong
__weak
__unsafe_unretained
__autoreleasing
通常来讲,咱们不太须要使用上面这些限定符。在使用移植工具的时候可能会看到那么几个,但新工程基本上不须要。 
  • __strong: 默认限定符,不须要显式指定。表示任何用alloc/init建立的对象在当前范围的生命期内得以保留。“当前范围”是指变量声明语句所在的两个大括号之间(方法、循环、块,等等)。
  • __weak: 表示对象能够随时被摧毁。只有当它被其它对象强引用时才有用。__weak变量在摧毁时,被设为nil。
  • __unsafe_unretained: 与__weak相似,但在摧毁时,不设为nil,保留原值(再也不指向有效的东西)。
  • __autoreleasing: 不要与autorelease搞混,它用于经过引用传递对象,好比,经过引用传递NSError对象: [myObject performOperationWithError:&tmp]。

来源http://clang.llvm.org/docs/AutomaticReferenceCounting.html#ownership

: 咱们发如今ARC下,@property中使用“retain”时(而不是“strong”),编译器并不会报错,并且能生成一样结果。但之后可能会变,因此仍是用“strong”吧。 


 

移植到ARC

Xcode 4.2提供了一个移植ARC的工具,还能够帮你将没法自动移植的代码手工转换过去。

1. 打开不兼容ARC的工程,进入Edit > Refactor > Convert to Objective-C ARC。

2. 选择须要转换的构建目标和文件(在后续步骤排除不须要转换的文件)

3. 运行预查,按下一步。

注: 按下一步后,LLVM编译器会开始构建,以便对工程进行分析。若是有错误,是没法进入下一步的。若是是第一次打开低版本Xcode创建的工程,请先执行清理。


 
其它翻译版本(1)

4. 检查一下工具建议的修改,并选择是否要排除某个文件。而后按下保存。 

注: 若是有文件没法移植,工具也会告诉你。并非全部文件都须要移植(包括库)。ARC是以文件为基础生效的,能够参考下文,看编译时如何把不须要开启ARC的文件排除在外。 

5. 工具会自动设置编译器的标识,开启ARC。能够查看工程的Build Settings确认这一点。 


 

引入不兼容ARC的代码 

Apple的文档说,“ARC能以文件为基础,与采用手工引用计数的代码进行交互。你能够在部分文件上使用手工引用计数。” 

它的意思是说,咱们可让一部分文件用ARC,另外一部分文件不用。下面是将文件批量排除在ARC以外的步骤。在我写下这篇文章的时候,还有许多流行的库都没有用ARC,为了解决这个问题,请按照下面的步骤作: 

  1. 在Xcode的工程树上,点击你本身的工程
  2. 点击Target
  3. 选择Build Phases标签
  4. 展开Compile Sources
  5. 选择须要排除在ARC外的文件
  6. 按下回车
  7. 输入-fno-objc-arc
  8. 再按下回车
  9. 如今,你选中的文件都有了-fno-objc-arc编译器标识,会被排除在ARC以外

 

我该用ARC吗?

若是你是Objective-C的新手,确定会欢迎ARC。一开始,有太多的东西要学习,有了ARC就不用担忧手工计数的问题。随着你开始接触一些已有的库,就会经历一些痛苦(译者注: 目前的第三方库不少不兼容ARC),但只要习惯了将它们排除在ARC以外,就没什么问题了。 

若是你不是新手,在没有ARC的年代已经玩的很high,那么可能会以为“我干吗要用它!”对你来讲,这多是正确的答案——就目前而言。由于,大多数流行的库都还没转到ARC下,并且ARC对Core Foundation的支持也很差。使用CF类的时候有一些限制,并且,移植代码的时候,为了让免费桥接生效,还须要加上一大堆限定符。 


 

在我看来,目前ARC已是可使用的状态了。不过,除非你对它很熟悉,不然仍是先用在新工程里会比较好。虽然ARC兼容iOS 4.0以上,但弱引用只在iOS 5.0以上才支持,因此如今仍是不要把全部东西都移植过去(有一些相关的文章,请参考最后的“资源”一节) 

至于性能方面,早前有报告指出用ARC以后速度会变快,我想多是因为减小对自动释放的依赖的缘故。虽然这彻底能够经过改进代码、更好地使用retain/release来实现,但我以为重点在于,ARC老是会自动帮你选择最优的方法。 


 

目前为止,仍是有不少使人苦恼的东西被移到ARC,但还不是所有。在长周末后咱们会离开一段时间以投入到新的项目,可是这是苹果一个“新的”推荐手段,因此你能够确定之后的设计决策会继续强大ARC而且让你们离开使用手动的引用计数 

ARC资源 

 

相关文章
相关标签/搜索