Objective-C ARC自动引用计数

本文转载自:http://www.onevcat.com/2012/06/arc-hand-by-hand/

ARC 自动引用计数

1.什么是 ARC

Automatic Reference Counting,自动引用计数,即ARC。ARC 是 iOS5 所引入的最大变革,用于代替传统的 MRC(手动引用计数)进行内存管理。

当 ARC 开启时,编译器将自动在代码合适的地方插入 retain,release 和 autorelease,而作为开发者,完全不需要担心编译器会做错,在工程中使用ARC非常简单:只需要像往常那样编写代码,只不过永远不写retain,release 和 autorelease 三个关键字,这是ARC的基本原则。

2.ARC 机制

只要某个对象被任一 strong 指针指向,那么它将不会被销毁。如果对象没有被任何 strong 指针指向,那么就将被销毁。

学习 ARC 很简单,在 MRC 时代你需要自己 retain 一个想要保持的对象,而现在不需要了。现在唯一要做的是用一个指针指向这个对象,只要指针没有被置空,对象就会一直保持在堆上。当将指针指向新值时,原来的对象会被 release 一次。这对实例变量,synthesize 的变量或者局部变量都是适用的。比如:

NSString *firstName = self.textField.text;

firstName 现在指向 NSString 对象,这时这个对象( textField 的内容字符串)将被 hold 住。比如用字符串 @“OneV” 作为例子(虽然实际上不应该用字符串举例子,因为字符串的 retainCount 规则其实和普通的对象不一样,大家就把它当作一个普通的对象来看吧…),这个时候 firstName 持有了 @”OneV”。

当然,一个对象可以拥有不止一个的持有者(这个类似 MRC 中的 retainCount > 1 的情况)。在这个例子中显然 self.textField.text 也是 @“OneV”,那么现在有两个指针指向对象 @”OneV” (被持有两次,retainCount=2,其实对 NSString 对象说 retainCount 是有问题的,不过anyway~就这个意思而已.)。

过了一会儿,也许用户在 textField 里输入了其他的东西,那么 self.textField.text 指针显然现在指向了别的字符串,比如 @“onevcat”,但是这时候原来的对象已然是存在的,因为还有一个指针 firstName 持有它。现在指针的指向关系是这样的:

只有当 firstName 也被设定了新的值,或者是超出了作用范围的空间(比如它是局部变量但是这个方法执行完了或者它是实例变量但是这个实例被销毁了),那么此时 firstName 也不再持有 @“OneV”,此时不再有指针指向 @”OneV”,在 ARC 下这种状况发生后对象 @”OneV” 即被销毁,内存释放。

类似于 firstName 和 self.textField.text 这样的指针使用关键字 strong 进行标志,它意味着只要该指针指向某个对象,那么这个对象就不会被销毁。反过来说,ARC 的一个基本规则即是,只要某个对象被任一 strong 指针指向,那么它将不会被销毁。如果对象没有被任何 strong 指针指向,那么就将被销毁。在默认情况下,所有的实例变量和局部变量都是 strong 类型的。可以说 strong 类型的指针在行为上和 MRC 时代 retain 的 property 是比较相似的。

既然有 strong,那肯定有 weak 咯~weak 类型的指针也可以指向对象,但是并不会持有该对象。比如:

__weak NSString *weakName = self.textField.text

得到的指向关系是:

这里声明了一个 weak 的指针 weakName,它并不持有 @“onevcat”。如果 self.textField.text 的内容发生改变的话,根据之前提到的”只要某个对象被任一 strong 指针指向,那么它将不会被销毁。如果对象没有被任何 strong 指针指向,那么就将被销毁”原则,此时指向 @“onevcat” 的指针中没有 strong 类型的指针,@”onevcat” 将被销毁。同时,在 ARC 机制作用下,所有指向这个对象的 weak 指针将被置为 nil。这个特性相当有用,相信无数的开发者都曾经被指针指向已释放对象所造成的 EXC_BAD_ACCESS 困扰过,使用 ARC 以后,不论是 strong 还是 weak 类型的指针,都不再会指向一个 dealloced 的对象,从根源上解决了意外释放导致的 crash。

注意类似下面的代码似乎是没有什么意义的:

__weak NSString *str = [[NSString alloc] initWithFormat:…]; 
NSLog(@"%@",str); //输出是"(null)"

由于 str 是 weak,它不会持有 alloc 出来的 NSString 对象,因此这个对象由于没有有效的 strong 指针指向,所以在生成的同时就被销毁了。如果我们在 Xcode 中写了上面的代码,我们应该会得到一个警告,因为无论何时这种情况似乎都是不太可能出现的。你可以把 weak 换成 strong 来消除警告,或者直接前面什么都不写,因为 ARC 中默认的指针类型就是 strong。

property 也可以用 strong 或 weak 来标记,简单地把原来写 retain 和 assign 的地方替换成 strong 或者 weak 就可以了。

@property (nonatomic, strong) NSString *firstName; 
@property (nonatomic, weak) id delegate;

ARC 可以为开发者节省很多代码,使用 ARC 以后再也不需要关心什么时候 retain,什么时候 release,但是这并不意味你可以不思考内存管理,你可能需要经常性地问自己这个问题:谁持有这个对象?

比如下面的代码,假设 array 是一个 NSMutableArray 并且里面至少有一个对象:

id obj = [array objectAtIndex:0]; 
[array removeObjectAtIndex:0]; 
NSLog(@"%@",obj);

在 MRC 时代这几行代码应该就挂掉了,因为 array 中 0 号对象被 remove 以后就被立即销毁了,因此 obj 指向了一个 dealloced 的对象,因此在 NSLog 的时候将出现 EXC_BAD_ACCESS。而在 ARC 中由于 obj 是 strong 的,因此它持有了 array 中的首个对象,array 不再是该对象的唯一持有者。即使我们从 array 中将 obj 移除了,它也依然被别的指针持有,因此不会被销毁。

一点提醒

ARC也有一些缺点,对于初学者来说,可能仅只能将ARC用在objective-c对象上(也即继承自NSObject的对象),但是如果涉及到较为底层的东西,比如Core Foundation中的malloc()或者free()等,ARC就鞭长莫及了,这时候还是需要自己手动进行内存管理。在之后我们会看到一些这方面的例子。另外为了确保ARC能正确的工作,有些语法规则也会因为ARC而变得稍微严格一些。

ARC确实可以在适当的地方为代码添加retain或者release,但是这并不意味着你可以完全忘记内存管理,因为你必须在合适的地方把strong指针手动设置到nil,否则app很可能会oom。简单说还是那句话,你必须时刻清醒谁持有了哪些对象,而这些持有者在什么时候应该变为指向nil。

ARC必然是Objective-C以及Apple开发的趋势,今后也会有越来越多的项目采用ARC(甚至不排除MRC在未来某个版本被弃用的可能),Apple也一直鼓励开发者开始使用ARC,因为它确实可以简化代码并增强其稳定性。可以这么说,使用ARC之后,由于内存问题造成的crash基本就是过去式了(OOM除外 :P)

我们正处于由MRC向ARC转变的节点上,因此可能有时候我们需要在ARC和MRC的代码间来回切换和适配。Apple也想到了这一点,因此为开发这提供了一些ARC和非ARC代码混编的机制,这些也将在之后的例子中列出。另外ARC甚至可以用在C++的代码中,而通过遵守一些代码规则,iOS 4里也可以使用ARC(虽然我个人认为在现在iOS 6都呼之欲出的年代已经基本没有需要为iOS 4做适配的必要了)、

总之,聪明的开发者总会尝试尽可能的自动化流程,已减轻自己的工作负担,而ARC恰恰就为我们提供了这样的好处:自动帮我们完成了很多以前需要手动完成的工作,因此对我来说,转向ARC是一件不需要考虑的事情