iOS - 從 UIViewController 生命周期講起
核心概念
本質:一組較為穩定的事件回調。
從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 之后。

浙公網安備 33010602011771號