理解这两者的差异,本质上是理解 “持有对象(UIKit)” 与 “描述状态(SwiftUI)” 之间的思维转换。UIKit 的生命周期围绕着 实例的创建与销毁,而 SwiftUI 的生命周期围绕着 身份(Identity)在渲染树中的存在。
以下是它们在生命周期各阶段的深度对比:
1. 生命周期事件对照表
| 阶段 | UIKit (UIViewController) | SwiftUI (View) |
|---|---|---|
| 初始化 | init() (仅一次) | init() (极其频繁,每次重绘都执行) |
| 加载完成 | viewDidLoad() | 无直接对应 (通常用 .onAppear 或 .task) |
| 即将显示 | viewWillAppear(_:) | .onAppear { ... } (近似) |
| 已经显示 | viewDidAppear(_:) | 无精确对应 (onAppear 在动画开始前触发) |
| 异步/并发 | 需在 viewDidAppear 自行启动 | .task { ... } (支持自动取消) |
| 即将/已经消失 | viewWill/DidDisappear | .onDisappear { ... } |
| 布局更新 | viewWillLayoutSubviews | 自动处理 (或用 GeometryReader 监听) |
2. 核心差异:从“实例化”到“身份化”
UIKit:对象的长跑
在 UIKit 中,UIViewController 是一个 Class(引用类型) 。它一旦被创建,其内存地址就是固定的。
- 防御式理解:你可以安全地在
viewDidLoad中设置一次性的监听或资源初始化,因为这个实例在整个页面存续期间不会变。
SwiftUI:结构体的瞬时重造
View 是 Struct(值类型) 。每当状态改变,SwiftUI 都会重新调用 init()。
- 警惕点:不要在 View 的
init()里写逻辑! 它是瞬时的。如果你在init里初始化一个网络请求,你的 App 会因为视图的频繁刷新而产生数千个冗余请求。
3. 三个关键的生命周期陷阱
A. onAppear 不等于 viewDidAppear
- 在 UIKit 中,
viewDidAppear保证了视图已经在屏幕上绘制完成。 - 在 SwiftUI 中,
.onAppear的触发时机可能早于视图渲染完成。如果你的逻辑依赖于精确的 UI 尺寸,.onAppear可能会拿到错误的frame。
B. .task 的革命性优势
UIKit 中处理异步任务很头疼:你需要在 viewDidAppear 开启,在 viewWillDisappear 手动取消,否则会造成内存泄漏或闭包回调崩溃。
- SwiftUI 方案:
.task钩子不仅在视图出现时启动异步任务,更会在视图从渲染树中移除时 自动发送取消信号。这是防御式异步编程的最佳实践。
C. @StateObject:生命周期的锚点
既然 View Struct 总是被销毁重造,状态怎么活下来?
- SwiftUI 引入了
@StateObject。即使 View 的init()跑了 100 次,StateObject的实例只会在该 View 第一次进入 Attribute Graph 时创建一次,并直到 View 彻底从渲染树移除才销毁。
4. 开发建议:如何平滑过渡?
如果你习惯了 UIKit 的思维,在 SwiftUI 中请遵循以下原则:
- 逻辑下沉到 ViewModel:将原本写在
viewDidLoad的初始化逻辑移到ObservableObject的init或.task中。 - 避免依赖 init 参数更新:因为
init频繁调用,如果参数里有复杂的计算,会导致性能瓶颈。 - 拥抱
.onChange:代替 UIKit 中的“观察者模式”,直接监听状态变化来触发副作用。
总结:UIKit 生命周期是在管理一个 “有生命的实体” ,而 SwiftUI 是在管理一个 “身份的在线状态” 。