ViewController Lifecycle — 是 UIKit 在 iOS 中管理屏幕时自动调用的方法序列。根据 Apple Documentation,每个 UIViewController 都会经历一组可预测的状态:从 View 的创建到它的出现和隐藏。理解这些方法的顺序和目的是 iOS 应用程序稳定运行的必要条件。
要点
ViewController Lifecycle — 是 UIViewController 在其存在期间从 UIKit 接收的一组方法。iOS 应用程序中的每个屏幕依次经历创建、View 加载、屏幕显示、消失和内存释放的阶段。UIKit 在每个阶段自动调用相应的方法,开发者通过重写这些方法来添加自己的逻辑。
UIViewController 的架构是 UIKit 的基础,即使在 SwiftUI 时代仍然具有相关性 — 许多项目仍然使用经典方法或混合架构。理解 Lifecycle 可以预测子视图何时可用、何时可以安全地修改布局以及在屏幕显示或隐藏时执行哪些操作。
每个生命周期方法都有特定的用途:有些在控制器的整个存在期间只调用一次,其他的则在每次出现或消失时调用。在方法之间混合逻辑会导致难以发现的错误:内存泄漏、数据更新不正确和不必要的网络请求。
六个方法构成 UIViewController 的完整生命周期。它们的调用顺序是固定的,不依赖于导航方式 — push、present 或 unwind segue 都遵循相同的调度。
loadView — 周期的第一个方法,在控制器的 View 尚不存在时调用。如果您使用 Storyboard,UIKit 会自动从 xib 文件加载 View。在通过编程创建界面时,您重写此方法,手动分配根 View。在大多数项目中,loadView 不会被触及 — 工作是在 viewDidLoad 中完成的。
只有在特定情况下才需要重写 loadView:当整个界面在没有 Storyboard 的情况下通过代码创建,或者根 View 必须是非标准类时。Apple 建议在重写时不要调用 super.loadView — 您完全接管 View 的创建。
override func loadView() {
view = UIView()
view.backgroundColor = .white
}
viewDidLoad — 周期中最常用的方法。在 View 加载到内存之后但在显示在屏幕上之前调用一次。在这里配置子视图、用数据填充表格、注册单元格以及订阅在控制器整个生命周期内有效的通知。
重要特性:viewDidLoad 在屏幕再次显示时不会再次调用。如果您需要在每次出现时更新数据 — 请使用 viewWillAppear。在 viewDidLoad 中只放置依赖于基本配置的一次性操作。
viewWillAppear 每次在 View 对用户变为可见之前调用。此方法接收 animated 参数,指示出现是否带有动画。在这里更新数据、重新加载表格、配置 NavigationBar 以及根据应用程序状态隐藏或显示元素。
使用 viewWillAppear 在屏幕之间同步状态:如果用户可能在前一个屏幕上更改了数据,此方法是更新界面的正确位置。每次 viewWillAppear 调用都在屏幕出现之前发生,即使是从子控制器返回时也是如此。
viewDidAppear 通知 View 已完全出现在屏幕上并且所有过渡动画已完成。此时,屏幕已准备好进行交互 — 用户看到完整的界面并可以与之交互。此方法适用于启动应在出现后开始的动画、启动计时器和跟踪分析展示。
与 viewWillAppear 不同,viewDidAppear 保证屏幕不仅可见而且完全渲染。如果您在 viewWillAppear 中启动动画,一些帧可能会被跳过,因为 UIKit 尚未完成过渡。对于流畅的动画,请使用 viewDidAppear。
viewWillDisappear 在 View 从屏幕消失之前调用 — 切换到另一个控制器、关闭模态窗口或最小化应用程序时。这是保存状态、取消订阅通知、停止活动进程和释放屏幕不可见时不需要的资源的正确位置。
重要提示:viewWillDisappear 不保证 View 最终会消失 — 手势可能被取消。因此,也要在 viewDidDisappear 中保存关键数据,后者仅在实际消失后调用。
viewDidDisappear 结束出现和消失的周期。在 View 已从屏幕隐藏后调用。在此方法中,动画完全停止,临时对象被删除,并且确认了在 viewWillDisappear 中开始的数据保存。
此方法也在控制器的 deinit 之前调用 — 如果您的 UIViewController 被销毁,viewDidDisappear 将是 deinit 调用之前的最后一个 Lifecycle 方法。在对象销毁之前,使用它进行最终清理。
调用顺序取决于屏幕出现的具体方式:第一次、返回时或模态显示时。让我们从 UIKit 的角度来看三个主要场景。
在屏幕第一次出现时,UIKit 经历完整的创建周期:调用 loadView,然后 viewDidLoad,之后出现动画开始。在动画期间调用 viewWillAppear,完成后调用 viewDidAppear。这是所有方法从 loadView 到 viewDidAppear 依次调用的唯一场景。
override func viewDidLoad() {
super.viewDidLoad()
print("viewDidLoad — View 已加载到内存")
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
print("viewWillAppear — 即将出现")
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
print("viewDidAppear — 屏幕完全可见")
}
当用户返回到前一个屏幕时,UIKit 不会再次调用 viewDidLoad — View 已加载到内存中。相反,在返回的屏幕上只触发 viewWillAppear 和 viewDidAppear,在当前屏幕上触发 viewWillDisappear 和 viewDidDisappear。loadView 和 viewDidLoad 被跳过,因为屏幕已经存在于导航堆栈中。
模态显示遵循相同的规则:新控制器在第一次出现时调用完整周期,当前控制器调用 viewWillDisappear 和 viewDidDisappear。在 dismiss 时顺序相反:返回的控制器再次触发 viewWillAppear 和 viewDidAppear,被隐藏的控制器触发结束方法。此行为对于 UIKit 中的所有过渡类型都是统一的。
让我们看看四个关键场景,其中理解 Lifecycle 直接影响代码质量和用户体验。对于每个场景,我们提供带有建议的示例。
viewDidLoad — 进行不依赖于屏幕可见性的初始设置的位置。在这里配置 collectionView、注册单元格的 nib 文件、创建数据源和布局。如果您从网络加载数据,在 viewDidLoad 中最好只发起请求,而在 viewWillAppear 中更新界面,当屏幕准备好显示时。
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(
MyCell.self,
forCellReuseIdentifier: MyCell.identifier
)
viewModel.loadInitialData()
}
使用 viewWillAppear 在每次屏幕出现时同步数据。例如,如果用户可能在前一个屏幕上更改了设置,则在此处更新显示的值、重新加载表格并调整 NavigationBar 状态。这确保屏幕在任何导航场景下始终显示最新数据。
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
tableView.reloadData()
navigationController?.setNavigationBarHidden(false, animated: animated)
}
viewDidAppear 非常适合启动应在用户看到屏幕后开始的动画。在此处还发送分析事件:屏幕展示、开始引导或开始视频播放。在过渡完成之前启动动画会导致卡顿的界面 — UIKit 来不及准备足够的帧。
在 viewWillDisappear 中保存草稿、停止计时器并取消订阅 NotificationCenter。这是屏幕仍然可见并且可用于需要用户上下文操作的最后时刻。对于关键数据,额外使用 viewDidDisappear 作为针对取消手势的保险。
生命周期方法的错误使用是 iOS 应用程序中最常见的错误来源之一。让我们看看开发人员在不同阶段使用 UIViewController 时的主要错误。
第一个错误 — 在使用 Storyboard 时在 init 或 loadView 中创建子视图。如果您使用 Interface Builder,不要无故重写 loadView。在存在 storyboard 的情况下在 loadView 中创建 View 会导致 xib 文件被忽略和空屏幕。
第二个错误 — 在 viewDidLoad 中订阅键盘通知而不取消订阅。如果您订阅了 UIResponder.keyboardWillShowNotification 但在屏幕隐藏时没有取消订阅,代码块将在控制器 deinit 之后仍然被调用 — 这是内存泄漏,可能导致应用程序崩溃。
第三个错误 — 在屏幕出现之前启动的计时器和网络请求。当 View 尚不可见时加载图像或运行动画 — 浪费资源。将视觉更新移到 viewWillAppear 或 viewDidAppear 中。
第四个错误 — 仅在 viewWillDisappear 中保存数据。在交互式 pop 手势中,用户可以开始滑动并取消它 — 方法被调用但屏幕没有消失。在 viewDidDisappear 或 applicationDidEnterBackground 的处理程序中复制关键保存。
常见问题
一次 — 在 View 加载到内存之后。在屏幕再次出现时,viewDidLoad 不会被调用。如果需要重新创建 View,控制器必须被销毁并重新创建。
UIKit 要求调用 super.viewDidLoad 以保证生命周期正常运作。没有它,可能会出现布局更新和过渡处理的问题。始终在方法中首先调用 super。
不建议。如果控制器是从 Storyboard 初始化的,UIKit 会自动从 xib 加载 View。重写 loadView 会取消此过程,您的 storyboard 将被忽略。
在 viewDidLoad 或 viewWillAppear 中订阅,在 viewWillDisappear 或 viewDidDisappear 中取消订阅,使用对 self 的弱引用以避免闭包中的内存泄漏。
强制退出会强行终止进程 — UIKit 来不及调用 Lifecycle 方法。要保存数据,请在 AppDelegate 中使用 UIApplication.willTerminateNotification 通知。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。