5-13.【OC】【Block】OC Block 的捕获规则,与 Swift closure 的 capture list 有哪些本质差异?

1 阅读2分钟

虽然 Objective-C 的 Block 和 Swift 的 Closure 在逻辑层面上非常相似,但它们的**捕获语义(Capture Semantics)**有着本质的差异。

最根本的区别在于:OC 默认是“值捕获”(拷贝),而 Swift 默认是“引用捕获”(绑定)。


1. 默认捕获行为的差异

Objective-C:默认捕获快照 (Value Copy)

在 OC 中,如果你捕获一个普通局部变量,Block 会在创建时将该变量的值拷贝到自己的结构体中。

  • 后果: 外部修改变量,Block 内部感知不到;Block 内部也无法修改该变量(除非加 __block)。

Swift:默认捕获引用 (Reference Binding)

Swift 的 Closure 默认会建立一个指向原始变量内存地址的引用。

  • 后果: 外部修改了变量,Closure 内部即时看到变化;反之,Closure 内部修改变量,外部也会变。

2. 底层实现机制

特性Objective-C BlockSwift Closure
基础变量捕获拷贝值到结构体成员。捕获变量的地址(隐式引用)。
修改外部变量必须显式使用 __block 将变量包装成结构体。原生支持,编译器会自动将变量“提升”到堆上。
Capture List没有这个语法。必须在外部手动定义 __weak原生支持 [weak self, val = x] 这种列表。

3. Capture List 的“降级”作用

Swift 的 Capture List [variable] 实际上是将 Swift 默认的引用捕获“降级”为 OC 式的值捕获。

Swift

var count = 0
let closure = { [count] in // Capture List 强制进行值拷贝
    print(count) 
}
count = 10
closure() // 输出 0 (OC 风格)

let closure2 = {
    print(count) // 默认引用捕获
}
closure2() // 输出 10 (Swift 默认风格)

4. 关键差异点:self 的捕获

在 OC 中:

你必须在 Block 外部手动声明 __weak typeof(self) weakSelf = self;。Block 捕获的是这个新定义的指针变量。如果你直接在 Block 内部写 self,它就会强引用。

在 Swift 中:

你可以直接在 Closure 的头部通过 Capture List 声明:

{ [weak self] in ... }

  • 本质差异: Swift 的这种写法是声明式的。它告诉编译器:在捕获 self 的那一刻,请不要建立强引用连接,而是建立一个弱引用连接。

5. 为什么要这么设计?

  • Objective-C 的保守: OC 诞生较早,为了保证栈内存的安全和简单,默认选择了“值拷贝”。引入 __block 是为了处理复杂的异步读写。
  • Swift 的直观: Swift 追求所见即所得。既然你在闭包里用了外部变量,那你肯定希望它是“实时”的。为了实现这一点,Swift 编译器在底层做了大量工作(比如自动把本该在栈上的变量挪到堆上,即 Escaping closures capture optimization)。

总结对照

  • OC Block: 像是一张照片(除非用了 __block 变成实时监控)。
  • Swift Closure: 默认是实时监控(除非用了 Capture List 把它拍成照片)。