iOS 层级的生命周期按三部分(App / UIViewController / UIView)
App(应用)生命周期 — 状态与关键回调
状态(概念)
Not running:未启动或已被系统终止。
Inactive:前台但不接收事件(短暂过渡,如来电、控制中心出现)。
Active:前台并接收事件(正常运行)。
Background:在后台仍可执行有限任务(有短暂后台时间或注册了后台任务)。
Suspended:被系统挂起,不再执行代码但仍驻留内存(可被系统随时回收)。
关键回调(iOS 13 之前 / 多场景之前) — UIApplicationDelegate
func application(_ application: UIApplication,didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Boolfunc applicationDidBecomeActive(_ application: UIApplication)
func applicationWillResignActive(_ application: UIApplication)
func applicationDidEnterBackground(_ application: UIApplication)
func applicationWillEnterForeground(_ application: UIApplication)
func applicationWillTerminate(_ application: UIApplication)
iOS 13+(多窗口/场景):生命周期以 Scene 为单位,使用 UISceneDelegate
:
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options: UIScene.ConnectionOptions)
func sceneDidBecomeActive(_ scene: UIScene)
func sceneWillResignActive(_ scene: UIScene)
func sceneWillEnterForeground(_ scene: UIScene)
func sceneDidEnterBackground(_ scene: UIScene)
func sceneDidDisconnect(_ scene: UIScene)
注:iPad / 多任务下每个
scene
(窗口)独立,必须使用 SceneDelegate 来处理与窗口相关的生命周期。
实用要点
didFinishLaunchingWithOptions
:做一次性初始化(依赖注入、第三方 SDK、数据库迁移等)。避免在这里做耗时同步工作。scene(_:willConnectTo:)
:创建/配置窗口、根 VC(iOS 13+)。applicationWillResignActive
/sceneWillResignActive
:短暂停止交互(保存少量状态、暂停游戏)。applicationDidEnterBackground
/sceneDidEnterBackground
:进行持久化、释放能在后台释放的资源;若需额外时间用beginBackgroundTask(expirationHandler:)
或使用BGTaskScheduler
注册后台任务。applicationWillTerminate
不总会被调用(用户强杀或系统崩溃可能不会触发),关键数据应在进入后台时保存。
UIViewController(控制器)生命周期 — 顺序与用途
典型调用顺序(展示时)
init(nibName:bundle:)
/init?(coder:)
(Storyboard/Nib)loadView()
(若未使用 storyboard,会在此创建view
)viewDidLoad()
—— 视图加载完成(只调用一次)viewWillAppear(_:)
—— 即将显示(每次出现都会调用)viewWillLayoutSubviews()
—— 布局子视图前viewDidLayoutSubviews()
—— 布局子视图后viewDidAppear(_:)
—— 已显示(适合启动动画、启动定时器)
离开/消失
viewWillDisappear(_:)
—— 即将消失(保存临时状态、停止输入)viewDidDisappear(_:)
—— 已消失(停止定时器、移除观察者、释放重资源)deinit
—— VC 被释放(非生命周期方法,但重要,确认被释放避免内存泄漏)
其他重要回调
loadView()
:如果你手写界面并不使用 Storyboard/Nib,可重写并self.view = someView
。不要在loadView()
执行复杂业务逻辑。viewIfLoaded
/isViewLoaded
:检查view
是否已加载。避免强制访问view
导致提前加载。prepare(for:sender:)
:segue 前执行数据传递。traitCollectionDidChange(_:)
:尺寸类 / 深色模式改变时回调。willMove(toParent:)
/didMove(toParent:)
:用于子控制器管理(addChild
/removeFromParent
时调用)。
子控制器管理
当把一个 VC 作为子 VC 嵌入时,正确调用顺序:
parent.addChild(child)
parent.view.addSubview(child.view)
child.didMove(toParent: parent)
移除时要先 willMove(toParent: nil)
-> removeFromParent() -> removeFromSuperview()(或相似顺序)。
什么时候做什么(实用建议)
初始化模型/一次性布局:
viewDidLoad()
。(创建 UI 元素、注册 table view cell、绑定数据)每次进入页面需要刷新:
viewWillAppear(_:)
(刷新数据、更新导航栏状态)。启动动画/开始计时器/开始播放:
viewDidAppear(_:)
(确保界面已在屏幕上)停止动画/暂停计时器/保存临时状态:
viewWillDisappear(_:)
或viewDidDisappear(_:)
。更新 Auto Layout 相关约束:
updateViewConstraints()
(或在viewDidLoad
添加约束,避免在 layoutSubviews 中频繁添加约束)。处理内存/监听:添加通知观察器在
viewDidLoad
或viewWillAppear
,移除在viewWillDisappear
或deinit
(取决于观察者的生命周期)。
常见坑
把耗时操作放到
viewDidLoad
且阻塞主线程 → UI 卡顿。应放到后台线程,完成后回到主线程更新 UI。viewDidLayoutSubviews
会多次调用(布局过程中),不要在里边重复添加约束或做昂贵操作。Modal 默认样式从 iOS 13 起变成 card / pageSheet,可能不会触发 presenting VC 的
viewWillDisappear
/viewDidDisappear
(因为 presenting VC 仍可见一部分),注意这一点在处理暂停/恢复逻辑时会有影响。
UIView(View)生命周期 — 关键方法与布局绘制
创建与加载
init(frame:)
:代码创建视图时调用。init?(coder:)
:从 storyboard / nib 加载时调用。awakeFromNib()
:从 nib 加载并连接 outlet 后调用(可以在这里做额外初始化)。
添加/移除与显示
willMove(toSuperview:)
/didMoveToSuperview()
:在被加入/移除父视图前后调用。willMove(toWindow:)
/didMoveToWindow()
:当视图加入某个 window(或从 window 移除)时调用 —— 启动/停止与屏幕显示相关的任务(如 CADisplayLink、动画、OpenGL/Metal 渲染线程)。
布局与约束
setNeedsLayout()
:标记需要布局 -> 系统会在下一个循环调用layoutSubviews()
。layoutIfNeeded()
:立即触发布局(如果有 pending layout)。layoutSubviews()
:手动布局子视图(在没有使用 Auto Layout 或部分自定义布局时重写)。会在 bounds 变化或setNeedsLayout()
后被多次调用。updateConstraints()
:当需要改变约束时重写并在最后调用super.updateConstraints()
。优点:约束更新是在约束更新阶段进行,避免在 layoutSubviews 中添加约束。
绘制
draw(_ rect: CGRect)
:用于自定义 Core Graphics 绘制(只在视图需要重绘时调用)。调用setNeedsDisplay()
来触发。避免在这里做非绘制逻辑或耗时计算(会阻塞主线程)。
其他
intrinsicContentSize
:对于基于内容尺寸的组件(如自定义 label/按钮),重写以支持自动布局。contentMode
与setNeedsDisplay()
:控制是否在 bounds 改变时需要重绘。UIResponder 的触摸事件(
touchesBegan
等)属于 view 的生命周期响应范畴。
快速参考表(什么时候做什么)
一次性初始化(内存中只需做一次):
init
/awakeFromNib
/viewDidLoad
每次页面出现前刷新 UI:
viewWillAppear(_:)
页面完全显示后启动动画或开始耗时 UI 操作:
viewDidAppear(_:)
停止动画 / 保存临时状态:
viewWillDisappear(_:)
/viewDidDisappear(_:)
添加观察者:通常在
viewDidLoad
或viewWillAppear
,对应在deinit
或viewWillDisappear
移除(与观察者生命周期对齐)布局相关变更:在
updateConstraints()
或layoutSubviews()
(但避免重复添加约束)释放重资源(大图片、缓存):进入后台或
viewDidDisappear
中清理
常见问题 & 排查建议
VC 不
deinit
:很常见,检查循环引用(闭包、Timer、NotificationCenter、delegate 未置 nil)。UI 在旋转/尺寸改变上错位:优先用 Auto Layout,必要时在
viewWillLayoutSubviews
调整常量并调用layoutIfNeeded()
。大量
viewDidLayoutSubviews
调用导致卡顿:检查是否在其中修改约束或触发setNeedsLayout()
的循环操作。App 后台行为不一致:不要依赖
applicationWillTerminate
;使用sceneDidEnterBackground
/applicationDidEnterBackground
做持久化与资源释放;如果需要后台长任务,使用BGTaskScheduler
或beginBackgroundTask
。