2-5.【OC】【内存管理】ARC 编译期插入了哪些代码?

0 阅读3分钟

在 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 函数调用。