ARC 深入
问题
ARC 是如何工作的?与 MRC 有什么区别?引用计数存储在哪里?
答案
MRC → ARC 演进
| MRC(手动) | ARC(自动) | |
|---|---|---|
| retain/release | 程序员手动调用 | 编译器自动插入 |
| 释放时机 | 程序员决定 | 编译器分析后自动插入 |
| 引入版本 | Objective-C 诞生起 | iOS 5 / Xcode 4.2 |
| 常见问题 | 忘记 release(泄漏)/多次 release(崩溃) | 循环引用 |
ARC 的本质
ARC 是编译时技术——编译器在适当位置插入 retain(引用计数 +1)和 release(引用计数 -1),当引用计数为 0 时调用 deinit 并释放内存。
class Person {
let name: String
init(name: String) { self.name = name }
deinit { print("\(name) is being deinitialized") }
}
var ref1: Person? = Person(name: "Alice") // RC = 1
var ref2 = ref1 // RC = 2
ref1 = nil // RC = 1
ref2 = nil // RC = 0 → deinit
引用计数存储位置
在 64 位系统中,对象的 isa 指针使用 nonpointer isa 优化:
┌─────────────────────────────────────────────┐
│ isa (64 bits) │
├─────────────────────────────────────────────┤
│ bit 0 : nonpointer (1 = 优化的 isa) │
│ bit 1 : has_assoc (关联对象) │
│ bit 2 : has_cxx_dtor (C++ 析构) │
│ bit 3-35 : shiftcls (类指针) │
│ bit 36-41 : magic │
│ bit 42 : weakly_referenced │
│ bit 43 : unused │
│ bit 44 : has_sidetable_rc │
│ bit 45-63 : extra_rc (内联引用计数,19 位) │
└─────────────────────────────────────────────┘
- extra_rc:存储引用计数 -1(最多 2^19 = 524288)
- 溢出时启用 Side Table(散列表)存储完整引用计数
weak引用也存储在 Side Table 中
Swift vs ObjC 的 ARC
| Swift ARC | Objective-C ARC | |
|---|---|---|
| 引用计数位置 | 内联 refcount + Side Table | isa extra_rc + Side Table |
| weak 实现 | Side Table / Swift WeakReference | Side Table |
| unowned | 不检查(unsafe)或检查(default) | 无(OC 无 unowned) |
| 值类型 | struct 无引用计数 | 无值类型概念 |
常见面试问题
Q1: ARC 和垃圾回收(GC)的区别?
答案:
- ARC:编译时插入引用计数操作,确定性释放(引用计数归零时立即释放),无暂停
- GC:运行时扫描可达对象,不确定性释放,可能有 STW(Stop The World)暂停
- ARC 无法自动解决循环引用(需要 weak/unowned),GC 可以
Q2: retain count 为什么不推荐直接获取?
答案:CFGetRetainCount 返回的值不可靠——系统框架内部可能持有额外引用、autorelease pool 中的对象引用计数未减少、编译器优化可能改变引用计数时机。调试应使用 Instruments 的 Allocations 或 Memory Graph。
Q3: Swift 的 struct 需要 ARC 吗?
答案:struct 本身不需要,因为它是值类型,在栈上分配和释放。但如果 struct 内包含引用类型属性(如 String、Array 底层的 buffer),复制 struct 时会增加内部引用类型的引用计数。