跳到主要内容

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 ARCObjective-C ARC
引用计数位置内联 refcount + Side Tableisa extra_rc + Side Table
weak 实现Side Table / Swift WeakReferenceSide 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 时会增加内部引用类型的引用计数。

相关链接