题目
Swift 中闭包捕获值类型与引用类型的区别及内存管理
信息
- 类型:问答
- 难度:⭐⭐
考点
闭包捕获语义,值类型与引用类型,内存管理,循环引用
快速回答
核心要点:
- 值类型(如结构体)被闭包捕获时创建独立副本,闭包内外修改互不影响
- 引用类型(如类实例)被捕获时是强引用,需注意循环引用风险
- 使用
[weak self]或[unowned self]捕获列表避免循环引用 - 局部变量通过堆分配实现闭包内外状态共享
原理说明
Swift 闭包会捕获其作用域内的变量:
- 值类型:默认捕获时创建独立副本(如 Int、String、Struct)
- 引用类型:捕获强引用(如 Class 实例),需手动处理内存管理
- 局部变量:编译器自动将其提升到堆内存,使闭包内外共享状态
代码示例
// 值类型捕获示例
struct ValueType { var num = 0 }
var value = ValueType()
let closure1 = { print(value.num) }
value.num = 10
closure1() // 输出 0(独立副本)
// 引用类型捕获示例
class RefType { var num = 0 }
let ref = RefType()
let closure2 = { print(ref.num) }
ref.num = 10
closure2() // 输出 10(共享引用)
// 循环引用示例
class Controller {
var handler: (() -> Void)?
init() {
handler = { self.doSomething() } // 强引用循环
}
func doSomething() {}
}
// 正确捕获方案
handler = { [weak self] in
self?.doSomething()
}最佳实践
- 对引用类型始终使用
[weak self]或[unowned self] - 值类型修改需用
var捕获:{ [var copy = value] in ... } - 逃逸闭包中避免直接修改外部值类型变量
常见错误
- 误认为值类型在闭包内修改会影响外部变量
- 忽略引用类型的循环引用导致内存泄漏
- 在并发环境中修改被多个闭包共享的引用类型数据
扩展知识
- 捕获列表原理:编译器将捕获变量转换为闭包对象的隐藏属性
- @escaping 闭包:必须显式处理
self引用 - 内存分配:局部值类型变量被闭包捕获时,Swift 自动在堆上分配存储
- inout 参数:闭包内不能直接捕获 inout 参数,需使用本地副本