iOS - UIViewController 生命周期
2025年10月30日 19:40
核心概念
本质:一组较为稳定的事件回调。
从VC的生命周期谈起,并扩展讲讲部分相关的API。
UIViewController
1. 初始化阶段
-
+initialize: 类的初始化方法 - 时机:仅 oc,且首次初始化时才会调用。
-
-init: 实例的初始化方法
- 如果是从 xib/storyboard 来的,调用会变成:
-
-initWithCoder: 在 nib 或 storyboard 解码时调用(对象刚被创建,未连接
IBOutlet)。 -
-awakeFromNib: 所有子对象实例化后,并且
IBOutlet都连接好后调用。
-
-initWithCoder: 在 nib 或 storyboard 解码时调用(对象刚被创建,未连接
- 如果是从 xib/storyboard 来的,调用会变成:
-
-loadView: 创建 vc 的 view - 时机:访问 vc 的 view 且 view 为空时调用
-
[super loadView] 默认实现:
- 设置了 nibName,通过 name 查找对应 nib:
- 有资源,则加载对应 nib。
- 没资源,会按照类名匹配主 bundle 下的同名 nib。
- 未设置 nibName,创建一个空白 view。
- 设置了 nibName,通过 name 查找对应 nib:
-
[super loadView] 默认实现:
2. 生命周期(相关流程)
stateDiagram-v2
[*] --> viewDidLoad: vc 的 view 创建完成后调用
viewDidLoad --> viewWillAppear: 视图即将可见
viewWillAppear --> viewIsAppearing: 视图的几何尺寸、safe area、trait 等环境已确认
viewIsAppearing --> updateViewConstraints: 约束更新,布局求解
updateViewConstraints --> viewWillLayoutSubviews: 在本轮布局阶段开始前回调(即将布局子视图)
viewWillLayoutSubviews --> viewDidLayoutSubviews: 在本轮布局阶段结束时回调
viewDidLayoutSubviews --> updateViewConstraints: 循环次数系统决定,可能 0 次可能多次
viewDidLayoutSubviews --> viewDidAppear: 过渡动画
viewDidAppear --> viewWillDisappear: 视图即将不可见
viewWillDisappear --> viewDidDisappear: 过渡动画
viewDidDisappear --> [*]: 视图不可见
⚠️:Appear 阶段的回调顺序并不是固定的,也可能是:
stateDiagram-v2 [*] --> updateViewConstraints updateViewConstraints --> viewIsAppearing viewIsAppearing --> viewWillLayoutSubviews viewWillLayoutSubviews --> viewDidLayoutSubviews viewDidLayoutSubviews --> [*]
可以看出-updateViewConstraints和-viewIsAppearing的顺序不一定是固定的。
- 原因:
- 二者不构成先后必然关系;
- 他们分别由“外观转场调度”与“布局引擎调度”驱动,是
UIKit中两条协同的流程;-
外观转场调度:外观/生命周期由
容器控制器(如导航)通过begin/endAppearanceTransition等驱动,负责“让谁消失/出现”的调度。-
触发外观回调:
viewWillAppear → viewIsAppearing → viewDidAppear、viewWillDisappear → viewDidDisappear。
-
触发外观回调:
-
布局引擎调度:约束/布局由
Auto Layout 引擎在布局阶段驱动,负责“计算 frame/安全区/约束应用”的调度。-
触发布局回调:
updateViewConstraints → viewWillLayoutSubviews → viewDidLayoutSubviews。
-
触发布局回调:
-
外观转场调度:外观/生命周期由
- 他们在主线程的同一个
RunLoop上交替工作:- 外观转场会引发几何/安全区变化,从而“标记”需要布局。
- 布局完成又为转场呈现提供正确的 frame。
3. 其他(不太常用)
-
销毁
-dealloc
-
内存告警
-
-didReceiveMemoryWarning:内存不足时,被 iOS 调用 -viewDidUnload:已弃用(iOS 3 ~ 6)
-
-
容器关系
-willMoveToParentViewController-didMoveToParentViewController
-
环境特征/尺寸变化
-
viewWillTransition(to:with:):旋转/分屏、pageSheet等拉动导致控制器视图 size 变化的场景。 -
traitCollectionDidChange(_:):布局方向变化(阿拉伯语 LTR -> RTL)、旋转/分屏等。
-
UIView(其实没有生命周期的概念,只是一些常用的事件回调)
1. 初始化
同 VC,只是没有 -loadView 而已。
2. 常用
-
层级与窗口
-
-willMoveToSuperview->-didMoveToSuperview -
-willMoveToWindow->-didMoveToWindow
-
-
约束与布局
-
-setNeedsLayout:标记需要布局, 等待下次RunLoop执行布局 -
layoutIfNeeded:若被标记为需要布局,则“立刻在当前RunLoop执行一次布局”。 -
layoutSubviews:布局过程中的回调,不能手动直接调。
-
什么操作会标记“需要布局”呢?
- 显示触发
- 调用
-setNeedsLayout方法。 - 调用
-setNeedsUpdateConstraints或修改约束后
- 调用
- 几何与层级变更(UIKit 内部会标记)
- 修改
frame/bounds/center/transform - 父视图的
bounds/safe area变化 - 视图 首次加入窗口 或 窗口变化(
-willMoveToWindow)
- 修改
- Auto Layout 相关
- 改
约束的 常量/优先级、启用/禁用 - 改
组件的 抗压缩/抗拉伸 优先级 -
translatesAutoresizingMaskIntoConstraints切换导致约束变化
- 改
- 内在尺寸(intrinsicContentSize)变化 -(视图“基于自身内容的天然尺寸”,不依赖外部约束)
- 调用
invalidateIntrinsicContentSize - 改变内在尺寸的属性更新:
text/font/numberOfLines等等。
- 调用
3. 其他(不太常用)
-
约束与布局
-
-setNeedsUpdateConstraints->-updateConstraints
-
-
环境变化
traitCollectionDidChangetintColorDidChangesafeAreaInsetsDidChangelayoutMarginsDidChange
-
渲染
setNeedsDisplay / setNeedsDisplay(_:)draw(_:)
VC 和 View 回调的交叉(切换 vc,创建加载 view 等)
回调顺序:
1. VC 的切换
// VC(A) 切换到 VC(B)
1. B -loadView
2. B -viewDidload
3. A -viewWillDisappear
4. B -viewWillAppear
5. B -viewWillLayoutSubviews
6. B -viewDidLayoutSubviews
7. A -viewDidDisappear
8. B -viewDidAppear
2. VC 与 View 的交叉
// 添加 viewB
1. VC - addSubview:viewB
2. viewB - willMoveToSuperview
3. viewB - didMoveToSuperview
// 出现 view
1. VC - viewWillAppear
2. viewB - willMoveToWindow
3. viewB - didMoveToWindow
4. VC - viewWillLayoutSubviews
5. VC - viewDidLayoutSubviews
6. viewB - layoutSubviews
7. VC - viewDidAppear
疑问:
为什么子 view 的
-layoutSubviews打印在-viewDidLayoutSubviews之后?
-viewDidLayoutSubviews的字面含义不是子 view 都做完-layoutSubviews了`?
- 其实顺序是正确的,并不矛盾。
-viewDidLayoutSubviews并不保证“所有子 view 的-layoutSubviews都已经执行完”,它只是“VC根视图这一轮布局周期结束”的回调。子视图的第一次布局可能被推迟到下一次布局循环,因此会出现在 viewDidLayoutSubviews 之后。