在 ARC(Automatic Reference Counting)模式下,编译器(Clang)并不是简单地替你手写了 retain/release,它实际上进行了一场**“静默的局部最优解计算”**。
编译器在编译期主要插入了以下四类底层函数调用,这些函数定义在 libobjc.A.dylib 库中。
1. 基础引用计数操作
当你操作对象指针时,编译器会根据变量的修饰符(__strong, __weak 等)插入对应的 C 函数:
-
objc_storeStrong:这是最频繁出现的函数。- 场景:给一个
__strong指针赋值。 - 逻辑:它内部封装了“先
retain新值,再release旧值”的逻辑。
- 场景:给一个
-
objc_release/objc_retain:- 场景:当局部变量作用域结束(大括号结束)时,编译器会自动在末尾插入
objc_release。
- 场景:当局部变量作用域结束(大括号结束)时,编译器会自动在末尾插入
2. 弱引用(Weak)管理
这是 ARC 相比 MRC 最大的进步。编译器会插入一套复杂的弱引用维护代码:
objc_initWeak:当你声明__weak id obj = ...时调用。objc_destroyWeak:当变量作用域结束时,清理弱引用表。objc_loadWeakRetained:这是关键。 当你使用__weak变量时,编译器会插入此函数。它会在运行时检查对象是否还在,如果在,就临时retain一下防止在读取瞬间被释放,使用完后再release。
3. 关联 Autorelease 的优化(黑科技)
这是 ARC 的性能精华。为了避免频繁进出 AutoreleasePool,编译器和运行时协同完成了一次“所有权握手”。
objc_autoreleaseReturnValue:插入在被调用方法的return处。objc_retainAutoreleasedReturnValue:插入在调用方的赋值处。
原理: 如果编译器发现一个对象刚被放入 autorelease 马上又要被外面 retain,它会直接在 TLS(线程局部存储)里做一个标记。
- 结果:对象根本不进入池子,直接从返回值“瞬移”给接收者。性能开销从“入池+出池”降到了近乎零。
4. 属性 Setter 的自动合成
当你写 @property (nonatomic, strong) NSObject *obj; 时,编译器不再生成 [obj retain] 这种源码,而是直接生成调用底层 objc_setProperty 的汇编逻辑。
5. 案例还原:编译器到底怎么改你的代码?
原始代码(你写的):
Objective-C
{
id obj = [[NSObject alloc] init];
}
编译器处理后的伪代码:
Objective-C
{
// 1. 调用 alloc 得到对象
id obj = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(obj, @selector(init));
// ... 在这里使用 obj ...
// 2. 在作用域结束前,编译器自动插入释放
objc_release(obj);
}
6. ARC 无法处理的死角
尽管编译器很聪明,但它在编译期无法检查到以下情况,必须由开发者手动处理:
- 循环引用(Retain Cycle) :它会严格按规则插入
release,但如果两个对象互相strong指向,它们的引用计数永远不会降到 0,编译器无法感知这种逻辑闭环。 - Core Foundation 对象:以
CF开头的 C 语言对象,ARC 不管,仍需手动调用CFRelease。
总结
ARC 并不是简单的“代码翻译”,它是**“静态分析 + 运行时辅助”**的结合。它在编译期通过分析变量的生命周期,插入了比 MRC 时期更高效、更安全的 C 函数调用。