虽然两者都基于自动引用计数(ARC) ,但 Swift 在设计上吸取了 Objective-C 的教训,将内存安全性从“开发者规约”提升到了“语言特性”层面。
它们处理循环引用的核心设计差异可以总结为以下四点:
1. 语法集成度:捕获列表 (Capture List) vs. 外部声明
这是开发中最直观的差异。
-
Objective-C (手动破坏性声明) :
为了打破 Block 的循环引用,你必须在 Block 外部手动声明一个弱引用变量。
Objective-C
__weak typeof(self) weakSelf = self; self.block = ^{ [weakSelf doSomething]; }; -
Swift (内联式声明) :
Swift 引入了捕获列表,允许在闭包的起始位置直接定义捕获规则。这种方式不仅代码整洁,更重要的是它将“如何捕获”变成了闭包定义的一部分。
Swift
self.closure = { [weak self] in self?.doSomething() }
2. 弱引用类型的丰富度:Unowned 的引入
Objective-C 只有一种安全的弱引用:__weak。而 Swift 区分了两种非强引用:
-
weak:与 OC 的__weak类似,安全、自动置nil、必须是可选型。 -
unowned(Swift 独有) :类似于 OC 的__unsafe_unretained,但不完全相同。- 设计初衷:用于解决两个对象“同生共死”且无需处理
Optional时的性能优化。 - 底层保护:
unowned会增加一个特殊的Unowned引用计数。虽然访问释放后的unowned会崩溃,但这种崩溃是“确定性”的(Runtime 陷阱),而不是 OC 那种随机的“野指针”非法访问。
- 设计初衷:用于解决两个对象“同生共死”且无需处理
3. 底层实现:Side Table (侧表) 的创建时机
在 Objective-C 中,__weak 对 Side Table 的依赖非常重。而 Swift 进行了性能优化:
-
Objective-C:一旦对象被弱引用,系统就会在全局哈希表中寻找或创建 Side Table。
-
Swift:对象最初只有 Strong 和 Unowned 计数。只有当对象第一次被
weak引用时,才会按需(On-demand)从内部引用计数空间“溢出”到一个外部的 Side Table。- 这种设计让那些没有弱引用的对象在内存排布上更加紧凑,读写效率更高。
4. 变量捕获的默认语义
正如我们之前讨论的,这是最容易坑到 OC 转 Swift 开发者的地方:
- OC Block:默认捕获变量的值 (Value) 。如果不加
__block,你在 Block 里改不了外部变量,也感知不到外部的变化。 - Swift Closure:默认捕获变量的引用 (Reference) 。这意味着 Swift 闭包默认比 OC Block 更容易通过“共享状态”意外产生循环引用(即便不涉及
self,两个闭包捕获同一个var变量也可能形成链条)。
5. 编译器检查的严苛程度
Swift 编译器比 OC 编译器“唠叨”得多:
- OC:如果你在 Block 里直接写
self,编译器通常只给一个淡淡的 Warning(或者干脆不给)。 - Swift:如果你在闭包里使用
self而没有解包或明确捕获关系,且该闭包是逃逸的(@escaping),编译器有时会强制要求你写出self.,作为一种“死亡提醒”,强迫你意识到这可能是一个循环。
总结对照表
| 特性 | Objective-C ARC | Swift ARC |
|---|---|---|
| 打破循环语法 | 外部声明 __weak | 内部捕获列表 [weak self] |
| 非强引用选项 | __weak, __unsafe_unretained | weak, unowned |
| 对 nil 的处理 | 自动置 nil (安全) | weak 置 nil / unowned 触发断言崩溃 |
| 内存结构 | 全局 SideTable 映射 | 对象内计数 溢出到 SideTable |
| 变量捕获语义 | 默认值捕获 | 默认引用捕获 |