2-19.【OC】【内存管理】Swift 中 closure 的捕获语义与 OC block 的关键差异是什么?

1 阅读3分钟

虽然 Swift 的 Closure(闭包)在很多概念上继承了 Objective-C 的 Block,但在底层捕获语义和编译器优化上,两者有着本质的区别。

最核心的差异可以总结为一句话:Swift 闭包默认捕获的是变量的“引用”而非“值”,且不再需要显式的 __block 修饰符。


1. 默认捕获语义:引用 vs. 值

这是两者最直观的区别。

  • OC Block:默认捕获的是变量的值(拷贝) 。如果你想在 Block 内部修改外部变量,必须手动加上 __block

  • Swift Closure:默认捕获的是变量的引用(存储地址)

    • 在 Swift 中,你不需要写任何特殊修饰符,就可以直接在闭包里修改外部的 var 变量。
    • 外部对变量的修改,闭包内部实时可见;反之亦然。

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:闭包可以捕获 structenum

    • 即便 struct 是值类型,闭包默认依然捕获它的引用。这意味着两个不同的闭包如果捕获了同一个 struct 变量,它们会共享这个 struct 的状态。这在 OC 中如果不使用 __block 是无法想象的。

5. 总结:关键差异对照表

特性Objective-C BlockSwift Closure
默认捕获方式值(拷贝)引用(共享存储)
修改外部变量必须使用 __block直接修改 (变量需为 var)
循环引用处理外部声明 __weak 变量内部 Capture List ([weak self])
性能优化手动 copy 到堆编译器自动判断 Escaping 状态
值类型支持仅限基本类型完整支持 structenum

💡 核心判定规则

在 Swift 中,你不再需要思考“这个变量是否需要跨栈生存”。你只需要关注:这个闭包是否会造成循环引用? 如果会,请直接在 [] 捕获列表中写下 weakunowned