虽然 Swift 的 Closure(闭包)在很多概念上继承了 Objective-C 的 Block,但在底层捕获语义和编译器优化上,两者有着本质的区别。
最核心的差异可以总结为一句话:Swift 闭包默认捕获的是变量的“引用”而非“值”,且不再需要显式的 __block 修饰符。
1. 默认捕获语义:引用 vs. 值
这是两者最直观的区别。
-
OC Block:默认捕获的是变量的值(拷贝) 。如果你想在 Block 内部修改外部变量,必须手动加上
__block。 -
Swift Closure:默认捕获的是变量的引用(存储地址) 。
- 在 Swift 中,你不需要写任何特殊修饰符,就可以直接在闭包里修改外部的
var变量。 - 外部对变量的修改,闭包内部实时可见;反之亦然。
- 在 Swift 中,你不需要写任何特殊修饰符,就可以直接在闭包里修改外部的
2. 内存管理:堆上的“自动升级”
在 OC 中,__block 变量被包装成一个复杂的结构体,并伴随栈到堆的迁移。而 Swift 的处理更加智能化:
- Escaping(逃逸)闭包:如果闭包可能在函数返回后才执行,编译器会自动将捕获的变量在堆上分配。
- Non-escaping(非逃逸)闭包:如果闭包仅在函数内部执行,编译器会进行栈上优化。因为闭包不会离开函数,它直接访问栈内存即可,无需昂贵的堆分配和引用计数操作。
3. Capture List(捕获列表):显式的所有权控制
Swift 引入了 Capture List [weak self, count = myCount],这是 OC Block 缺失的语法糖,极大地提高了安全性:
-
OC 的痛点:为了打破循环引用,你必须在 Block 外面写一行丑陋的
__weak typeof(self) weakSelf = self;。 -
Swift 的优雅:你直接在闭包开头声明如何捕获。
Swift
// Swift 直接在闭包声明中解决问题 let closure = { [weak self] in self?.doSomething() } -
值快照:如果你想在 Swift 中模拟 OC 的“值拷贝”行为,也可以利用捕获列表:
Swift
var count = 10 let closure = { [count] in // 这里的 count 是被拷贝的快照 print(count) } count = 20 closure() // 输出 10,而不是默认的 20
4. 对 Value Type(值类型)的特殊处理
这是 Swift 作为强类型语言的特殊之处。
-
OC:Block 只能捕获对象或基本 C 类型。
-
Swift:闭包可以捕获
struct或enum。- 即便
struct是值类型,闭包默认依然捕获它的引用。这意味着两个不同的闭包如果捕获了同一个struct变量,它们会共享这个struct的状态。这在 OC 中如果不使用__block是无法想象的。
- 即便
5. 总结:关键差异对照表
| 特性 | Objective-C Block | Swift Closure |
|---|---|---|
| 默认捕获方式 | 值(拷贝) | 引用(共享存储) |
| 修改外部变量 | 必须使用 __block | 直接修改 (变量需为 var) |
| 循环引用处理 | 外部声明 __weak 变量 | 内部 Capture List ([weak self]) |
| 性能优化 | 手动 copy 到堆 | 编译器自动判断 Escaping 状态 |
| 值类型支持 | 仅限基本类型 | 完整支持 struct 和 enum |
💡 核心判定规则
在 Swift 中,你不再需要思考“这个变量是否需要跨栈生存”。你只需要关注:这个闭包是否会造成循环引用? 如果会,请直接在 [] 捕获列表中写下 weak 或 unowned。