若你只想记住一句话:“当闭包生命周期可能长于 self 时,永远不要使用 unowned。”
从一段崩溃代码说起
// 看似无害的「定时器」
class HomeViewController: UIViewController {
private var timer: Timer?
override func viewDidLoad() {
super.viewDidLoad()
// ⚠️ 崩溃隐患:unowned self
timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [unowned self] _ in
self.updateUI() // 若 1s 内用户退出页面 → 野指针崩溃
}
}
deinit { print("HomeViewController 已释放") }
private func updateUI() { /* ... */ }
}
运行步骤:
- 用户进入页面 → Timer 持有闭包 → 闭包持有 unowned self。
- 用户在 0.8s 时点击返回 →
HomeViewController被释放 → 内存地址变野。 - 1s 到时系统再回调闭包 → 访问野指针 →
EXC_BAD_ACCESS。
unowned 与 weak 的底层区别
| 关键字 | 运行时结构体 | 引用计数变化 | 对象销毁后访问结果 | 开销 |
|---|---|---|---|---|
weak | WeakReference | 不 +1 | 自动置 nil | 高 |
unowned | Unmanaged | 不 +1 | 野指针(非 nil) | 低 |
Swift 源码层面(swift/stdlib/public/core/HeapObject.cpp):
weak会被登记到 side-table,对象销毁时所有 weak 指针被批量置 nil。unowned仅保存原始地址,销毁时 不做任何后置清理,访问时通过swift_unknownObjectUnownedTakeStrong尝试“复活”对象;若失败则直接崩溃。
官方文档没说清楚的 3 个场景
- 动画/网络回调
// UIView.animate 的逃逸闭包
func flyEmoji() {
UIView.animate(withDuration: 2, animations: { [unowned self] in
self.emojiView.alpha = 0
}) // 若 2s 内用户退出页面 → 崩溃
}
- Combine / RxSwift
// Combine 订阅
cancellable = publisher
.sink { [unowned self] value in
self.handle(value) // 上游发值时 self 可能已死
}
- Swift Concurrency
// Task 闭包
Task { [unowned self] in
await self.loadData() // 任务未结束时 self 被释放 → 崩溃
}
工程级“逃生舱” checklist
已将您提供的场景与推荐写法整理为 Markdown 表格:
| 场景 | 推荐写法 | 备注 |
|---|---|---|
| 定时器 | [weak self] + timer.invalidate() on deinit | 双重保险 |
| 动画 | [weak self] + 判断 self?.viewIfLoaded != nil | 避免操作已卸载视图 |
| Combine | [weak self] + cancellable.cancel() on deinit | 及时取消订阅 |
| SwiftUI | 直接使用 struct + @State | 无引用循环问题 |
扩展:用“WeakBox” 消灭可选链
/// 让 weak 引用也能像 unowned 一样方便,但更安全
final class WeakBox<T: AnyObject> {
weak var value: T?
init(_ value: T) { self.value = value }
}
extension HomeViewController {
func loadData() {
let box = WeakBox(self)
timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
guard let self = box.value else { return }
self.updateUI()
}
}
}
小结
- unowned 的唯一优点是 微不可计的性能提升;
- 当闭包寿命 可能 超过 self 寿命时,unowned = 定时炸弹;
- 在 UI 层、网络层、异步层,默认使用 weak;
- 若性能 profiling 证明 weak 成为热点,再考虑局部替换为 unowned,并加单元测试覆盖生命周期边界。
非逃逸闭包(non-escaping closure)—— 唯一 100% 安全的 unowned 场景
Swift 默认闭包是非逃逸的,编译器可以证明:闭包返回后,self 一定还活着。
// 数组的 forEach(非逃逸)
class Counter {
var sum = 0
func accumulate(_ nums: [Int]) {
// 安全:forEach 同步执行,执行完 self 才退出作用域
nums.forEach { [unowned self] n in
self.sum += n
}
}
}
底层原理:
SIL(Swift Intermediate Language)在 optimize_lifetime 阶段会插入 fix_lifetime 指令,确保 self 在闭包调用期间不会被提前释放。
手动延长生命周期 —— withExtendedLifetime
// 同步网络请求回调,临时用 unowned 避免循环,但又要防崩溃
final class SyncAPIClient {
func getUser() -> User? {
var result: User?
// 保证 self 在 block 返回前不会被释放
withExtendedLifetime(self) {
self.networkBlockAndWait { [unowned self] data in
result = self.parse(data) // 安全:self 被延长
}
}
return result
}
}
withExtendedLifetime 的源码实现:
@inlinable
public func withExtendedLifetime<T, Result>(
_ x: T,
_ body: () throws -> Result
) rethrows -> Result {
defer { _fixLifetime(x) } // 防止编译器提前释放
return try body()
}