题目
Swift 中的内存管理与循环引用陷阱
信息
- 类型:问答
- 难度:⭐⭐⭐
考点
ARC原理,循环引用解决,weak与unowned区别,闭包捕获列表
快速回答
在Swift中处理循环引用的核心要点:
- 使用
weak打破父-子对象间的循环引用(子对象引用父对象时) - 使用
unowned当引用对象生命周期相同或更长时(需确保不会访问已释放对象) - 在闭包中使用捕获列表
[weak self]或[unowned self]避免闭包强持有 - 对于值类型(如struct)无需担心循环引用
原理说明
Swift使用自动引用计数(ARC)管理内存。当两个或多个对象相互强引用时形成循环引用,导致内存泄漏。解决方案:
- weak:允许引用变为nil的弱引用(自动置nil)
- unowned:假定引用始终有效,访问已释放对象会触发运行时崩溃
- 闭包捕获列表:显式声明闭包捕获的引用关系
代码示例
class Parent {
var child: Child?
deinit { print("Parent deallocated") }
}
class Child {
// 正确:weak打破循环
weak var parent: Parent?
// 危险:unowned需确保parent生命周期更长
// unowned let parent: Parent
var closure: (() -> Void)?
init(parent: Parent) {
self.parent = parent
// 闭包循环引用示例
closure = { [weak self] in
// 必须使用weak self或unowned self
print(self?.parent)
}
}
deinit { print("Child deallocated") }
}
// 测试
var parent: Parent? = Parent()
parent?.child = Child(parent: parent!)
parent = nil // 正确释放最佳实践
- 优先使用
weak避免野指针崩溃 - 仅当对象生命周期严格长于当前对象时使用
unowned - 闭包中强制使用捕获列表声明引用关系
- 使用
weak后需处理可选类型(self?)
常见错误
- 在闭包中直接使用
self导致循环引用 - 错误使用
unowned访问已释放对象 - 忽略
[weak self]后对self?的可选处理 - 未注意协议声明中的
class约束导致值类型循环引用误判
扩展知识
- 闭包捕获原理:闭包是引用类型,默认强捕获所有外部变量
- weak vs unowned性能:
unowned无额外开销,但风险更高 - 调试技巧:使用Xcode Memory Graph Debugger检测循环引用
- 新特性:Swift 5.0引入
@unknown和weakable提案讨论更安全的unowned