闭包会捕获它所引用的变量,使它们保持存活。当一个闭包捕获了 self,而 self 又持有该闭包(作为存储属性)时,它们会彼此永远保持存活——这就是一个泄漏内存的循环引用。像 [weak self] 这样的捕获列表能打破这个循环。
当一个对象的引用计数归零时,ARC 会释放它。一次强捕获会使该计数加一。
final class Downloader {
var onDone: (() -> Void)? // stored closure -> strong
func start() {
// BAD: closure captures self strongly, self holds closure -> cycle
onDone = { self.cleanup() }
// GOOD: weak capture, self can deallocate
onDone = { [weak self] in
guard let self else { return } // safely unwrap; bail if gone
self.cleanup()
}
}
func cleanup() {}
}
[weak self]——self 变成一个 optional;如果对象已被释放,它就是 nil。当对象可能比闭包活得更久时,这是安全的默认选择。[unowned self]——非 optional,无 ARC 开销,但如果 self 已经不存在则会崩溃。仅在保证 self 比闭包活得更久时使用。Task { [weak self] in
let data = await self?.fetch() // self? short-circuits if deallocated
}
非逃逸闭包(如 map、filter、forEach)同步运行并立即释放捕获的内容——没有循环,不需要 [weak self]。风险在于逃逸的、被存储的闭包:completion handler、Timer、NotificationCenter、Combine 的 sink、delegate 块。
循环引用是 iOS 中最常见的内存泄漏,而且在 Instruments 显示内存持续增长之前它们是不可见的。准确地知道闭包何时逃逸、以及为什么需要 [weak self]——而不是到处条件反射式地用它——正是理解 ARC 的工程师与照本宣科者之间的区别。
一个包含详细解答的 IT 面试题库——从初级到高级。
捐赠