【iOS】自动引用计数 (循环引用)

历史版本程序员

ARC(Automatic Reference Counting,自动引用计数)极大地减小了Cocoa开发中的常见编程错误:retainrelease不匹配。ARC并不会消除对retainrelease的调用,而是把这项本来大都属于开发者的工做移交给了编译器。这样作的好处是显而易见的,可是必须知道retainrelease是仍然在使用的。ARC并不等同垃圾回收。思考下面这段代码,它对一个实例变量赋值:编程

1
2
3
@property (nonatomic, readwrite, strong) NSString *title;
...
_title = [NSString stringWithFormat:@"Title"];

在非ARC环境中,_title没有被保留过,那么赋给它的NSString就是autorelease的,会在事件循环结束时被释放。以后若是再次访问_title,程序就会崩溃。这类错误极其常见,调试起来会异常困难。另外,若是_title以前已经有一个值,那旧的值就会由于没有被释放掉而引发内存泄漏。markdown

使用ARC,编译器会在合适的位置自动插入一些代码,下面的代码与前面的代码是等同的:工具

1
2
3
4
id oldTitle = _title;
_title = [NSString stringWithFormat:@"Title"];
[_title retain];
[oldTitle release];

retainrelease仍然会被调用,因此有一些开销,在release的时候可能还会调用dealloc方法。这段代码与程序员手动调用retainrelease的代码在运行结果上是彻底一致的。垃圾回收机制是在运行时起做用的,会影响运行效率,而ARC是在编译时插入内存管理代码,不影响运行时效率,所以内存回收比垃圾回收时的效率要高,可以提高系统性能。正如其余一些编译器优化方式同样,这种编译器能够自由地以多种方式优化内存管理,而让程序员手动去作这些工做是不现实的。在多数状况下,使用ARC生成内存管理代码的程序比程序员手工添加内存管理代码的对等程序运行更快!性能

ARC不是垃圾回收,尤为是它不能像Snow Leopard中的垃圾回收机制那样处理循环引用(保留)。图3-1中的对象A与对象B之间存在循环保留。优化

图3-1 循环保留atom

在Snow Leopard的垃圾回收机制下,若是从“外部对象”到“对象A”的引用连接中断了,对象A和对象B都会被销毁,由于它们从程序中孤立出去了。而在ARC中,对象A和对象B都不会被销毁,由于它们的引用计数都大于0。所以,在iOS开发中,必需要作好对强引用(strong reference)的跟踪管理以避免出现循环引用。spa

属性关系有两种主要类型:strongweak,至关于非ARC环境里的retainassign。只要存在一个强引用,对象就会一直存在,不会被销毁。强引用相似于C++中的shared_ptr,只不过管理引用计数的代码是在编译时生成的,而shared_ptr是在运行时经过操做符重载肯定的。指针

Objective-C中一直存在循环引用的问题,但在实际应用中不多出现循环引用。对于过去那些使用assign属性的地方,在ARC环境中要使用weak代替。大部分引用循环是由委托(delegate)引发的,因此应该老是把delegate属性声明为weak。当引用的对象被销毁以后,weak引用会被自动置为nil,与assign相比这是一个巨大的进步,由于assign能够指向被释放掉的内存,致使程序崩溃。调试


在ARC出现之前,合成属性(synthesized property)的默认存储类型是assign;而在ARC中,默认的存储类型是strong。这在进行代码转换时可能会形成一些困惑。建议为全部属性显式指明存储类型。 循环保留产生的另外一个主要缘由是块,其介绍详见第23章。


将代码转换到ARC的时候,要注意如下两点。

  •   不要再使用retainreleaseautorelease,能够直接将这些代码删除。ARC在编译时会自动在合适的位置插入内存管理代码。
  •   若是dealloc方法只是用于释放实例变量,那么dealloc方法也没有存在的必要了,能够直接删掉。在任何状况下都不须要再使用release,ARC会自动处理好这些事情。若是仍然须要使用dealloc来作一些其余的事情(好比移除KVO观察),记得不要再调用[super dealloc],不然编译器会给出错误信息。

正如前面所说的,ARC并非垃圾回收,它是编译器的一种功能,能够在代码中合适的位置自动插入调用retainrelease的语句。这也意味着,若是现存的使用手动内存管理方式的代码很好地遵照了命名约定,那么这些代码跟ARC就是彻底互通的。举例来讲,若是调用了一个名为copySomething的方法,ARC会认为这个方法会对返回对象的引用计数加1,若是有必要,ARC会在合适的位置插入一个release。引用计数的加1操做既能够在ARC代码中进行,也能够在某段手动进行内存管理的代码中进行,这对于ARC来讲是无所谓的。

可是,若是没有遵照Cocoa命名约定,这种状况下就会出错。好比有一个名为copyRight的方法,它以autorelease的形式返回一个版权信息的字符串,这种状况下ARC的行为取决于调用代码和被调用代码是否都是使用ARC进行编译的。

方法名是以copy开头的,所以ARC认为copyRight方法会对返回对象的引用计数加1。若是copyRight方法的代码与调用copyRight方法的代码都是使用ARC编译的,ARC就会在copyRight方法代码中添加一个retain语句,同时在调用copyRight方法的代码中添加一个release语句,这种状况下程序依然会正常工做。虽然对执行效率有一点点影响,可是程序不会崩溃,也不会形成内存泄漏。

然而,若是调用copyRight方法的代码是使用ARC编译的(ARC就会在代码后边添加一个release语句),而copyRight方法代码没有使用ARC编译的话(方法体中就没有retain语句),程序就会崩溃。反过来,若是copyRight方法代码使用ARC编译(ARC就会在方法代码中添加一个retain语句),而调用代码没有使用ARC编译的话(调用代码中就不会有release语句),代码就会形成内存泄漏。

最好的解决办法就是遵照Cocoa的命名约定。在这个例子中,若是将方法名改成copyright,就能够彻底避免这种问题。ARC根据驼峰式大小写方式对方法的名称进行单词分隔,而后使用相应的内存管理规则。

若是对一个错误命名的方法进行重命名是不现实的,那么还能够在方法声明中使用NS_RETURNS_RETAINED或者NS_RETURNS_NOT_RETAINED修饰符来告诉编译器应该使用哪一种内存管理规则。这些修饰符是在NSObjCRuntime.h中定义的。

为了让编译器可以恰当地添加retainrelease语句,编写ARC代码时须要注意如下四点。

  • 不要调用retainreleaseautorelease。直接将这些代码删除,这是最简单的规则。另外,也不能够覆盖这几个方法(永远不要试图去覆盖这几个方法)。若是你想实现Singleton模式,请阅读第4章,了解如何在不覆盖这些方法的状况下实现Singleton模式。

  • C语言结构体中不要有对象指针。这种状况不多出现。可是若是C语言结构体中确实有一个对象,要么把它存储到另外一个对象中,要么就把它转换成void*类型(下一条规则会介绍如何转换到void*)。能够在任什么时候候经过调用free方法销毁一个C语言结构体,这会影响到对结构体中对象的自动跟踪。

  • idvoid*类型只能经过桥接转换进行转换。在使用了Core Foundation的代码中会有不少这种状况。第27章会详细介绍桥接转换及如何结合ARC来使用。

  • 不要使用NSAutoreleasePool。不要手动建立本身的自动释放池,直接把须要本身的池的代码放入@autoreleasepool{}代码块中便可。若是你有一些特殊的代码用于控制自动释放池的释放,那些代码实际上极可能是没必要要的。@autoreleasepoolNSAutoreleasePool快了大约20倍。

对于大部分代码来讲,只要遵照这些规则就不会有任何问题。在Xcode中有一个小工具能够替你完成大部分代码转换的工做,它位于Edit → Refactor → Convert to Objective-C ARC。

ARC应该是Objective-C中继自动释放池以后最重要的改进了,应该尽量地使用。若是不能将代码所有转换到ARC,就尽量地转换。使用ARC编译的程序速度更快,bug更少,也比手动内存管理代码更容易编写。如今就开始使用ARC吧!

相关文章
相关标签/搜索