过渡到 ARC 的发布说明

Transitioning to ARC Release Notes

iOS MRC 内存管理的基本原则iOS MRC 内存管理的实用技巧 之后,终于可以将到 ARC 了。

ARC 的全称是 Automatic Reference Counting,自动引用计数。

之前聊 MRC 时提到,管理引用计数这个工作,不应该交给开发者去做,太繁琐,而且容易出错。完全可以放到编译器去做。ARC 就是这样,会在编译器自动帮助开发者添加合适的内存管理代码。

开发者再也不同到处 retain release 了!

如图所示,在编译器,会自动加上 retain release 等内存管理代码,开发者只需要专注于实现业务功能即可。

ARC 的版本兼容

提到新功能,就不得不提兼容性。最坑的就是要兼容旧版本。ARC 倒是很舒服了,目前微信已经最低支持 iOS 10 了,而且 xCode 也发布了 11.4 正式版,已经完全支持 ARC 了,所以不需要兼容旧版本。

ARC 的新规则

ARC 规定了一些和 MRC 不同的规则,如果开发者为遵循,则会直接无法编译通过。

  • 不可以直接调用 dealloc 方法,不可以调用和实现 retain、release、retainCount、autorelease。
  • 可以实现 dealloc 方法做一些释放资源的逻辑,但是不可以调用 [super dealloc],否则无法编译通过。对 super 的调用由编译器自动完成。
  • ARC 是适用于 OC 对象,Core Function 对象的内存管理还要继续使用 CFRetain, CFRelease 等方法。
  • 不可以使用 NSAllocateObject 和 NSDeallocateObject,创建对象使用 alloc,释放对象由系统自动完成。
  • 不可以使用 C 结构的对象指针,不能使用 struct,而是使用 OC 的类作为替代。
  • id 和 void * 之间不能随意转换。在 Cocoa 和 Core Function 对象交互时需要处理内存管理问题,参考后面提到的「管理 TFB」。
  • 不能使用 NSAutoreleasePool,可以使用 @autoreleasepool 块,后者比前者更高效。
  • 不再需要使用 NSZone,新的 OC 运行时会忽略 NSZone。

为了和 MRC 的代码共存,ARC 在函数命名上加了一些限制:

  • getter 的函数名不能以 new 开头,比如声明一个属性,属性名不能以 new 开头,除非特殊指定 getter 的名字。
// Won't work:
@property NSString *newTitle;
 
// Works:
@property (getter=theNewTitle) NSString *newTitle;

ARC引入了新的生命周期限定符

ARC引入了一些对象的生命周期限定符,以及弱引用。弱引用不会让对象的引用计数 +1,并且在对象被释放时弱引用会自动变成 nil。

ARC 并不能防止循环引用的出现,可以使用弱引用解决这个问题。

Property 的属性

增加了 strong 和 weak 两种属性,其中对象默认是 strong 的。


// The following declaration is a synonym for: @property(retain) MyClass *myObject;
@property(strong) MyClass *myObject;
 
// The following declaration is similar to "@property(assign) MyClass *myObject;"
// except that if the MyClass instance is deallocated,
// the property value is set to nil instead of remaining as a dangling pointer.
@property(weak) MyClass *myObject;

变量限定符

ARC 增加了下面几种限定符:

  • __strong 默认值,只要有指针指向,就不会被释放
  • __weak 不持有对象,指向一个对象时不影响对象的回收,对象被回收后变成 nil,常用于处理循环引用问题
  • __unsafe_unretained 不持有对象,指向一个对象时不影响对象的回收,对象被回收后变成野指针,常用于处理循环引用问题
  • __autoreleasing 在最近的自动释放池结束的时候被释放,常用于函数中传入二级指针 id *

限定符的正确用法格式如下:

ClassName * qualifier variableName;

例如:

MyClass * __weak myWeakReference;
MyClass * __unsafe_unretained myUnsafeReference;

其他形式都是无效的,会被编译器忽略。

在使用 __weak 的时候要注意对象被立即释放的问题:

// string 的引用计数为 0,会被自动释放
NSString * __weak string = [[NSString alloc] initWithFormat:@"First Name: %@", [self firstName]];
NSLog(@"string: %@", string);

关于二级指针 id *,有个点需要留意下。

NSError *error;
BOOL OK = [myObject performOperationWithError:&error];
if (!OK) {
    // Report the error.
    // ...

上面这段代码很常见,其中 &error 是二级指针,编译器会自动加上 __autoreleasing 的逻辑。编译后的代码会变成这样:

NSError * __strong error;
NSError * __autoreleasing tmp = error;
BOOL OK = [myObject performOperationWithError:&tmp];
error = tmp;
if (!OK) {
    // Report the error.
    // ...

使用限定符来避免循环引用