iOS - UIViewController 生命周期

核心概念

本质:一组较为稳定的事件回调。

VC的生命周期谈起,并扩展讲讲部分相关的API

UIViewController

1. 初始化阶段

  1. +initialize : 类的初始化方法 - 时机:仅 oc,且首次初始化时才会调用

  2. -init: 实例的初始化方法

    • 如果是从 xib/storyboard 来的,调用会变成:
      1. -initWithCoder : 在 nib 或 storyboard 解码时调用(对象刚被创建,未连接 IBOutlet)。
      2. -awakeFromNib : 所有子对象实例化后,并且IBOutlet都连接好后调用。
  3. -loadView : 创建 vc 的 view - 时机:访问 vc 的 view 且 view 为空时调用

    • [super loadView] 默认实现 :
      1. 设置了 nibName,通过 name 查找对应 nib:
        1. 有资源,则加载对应 nib。
        2. 没资源,会按照类名匹配主 bundle 下的同名 nib。
      2. 未设置 nibName,创建一个空白 view。

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 → viewDidAppearviewWillDisappear → 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
  • 环境变化
    • traitCollectionDidChange
    • tintColorDidChange
    • safeAreaInsetsDidChange
    • layoutMarginsDidChange
  • 渲染
    • setNeedsDisplay / setNeedsDisplay(_:)
    • draw(_:)

VC 和 View 回调的交叉(切换 vc,创建加载 view 等)

回调顺序:

1. VC 的切换

swift 复制代码
// 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 的交叉

swift 复制代码
// 添加 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 之后。
相关推荐
JordanHaidee1 个月前
【Rust GUI开发入门】编写一个本地音乐播放器(11. 支持动态明暗主题切换)
前端·ui kit
songgeb3 个月前
UIScene in iOS
ios·ui kit
大熊猫侯佩4 个月前
UIKit 在 UICollectionView 中拖放交换 Cell 视图的极简实现
swift·apple·ui kit
仿生狮子7 个月前
Reka UI 是个啥?
vue.js·nuxt.js·ui kit
Sinyu10127 个月前
Flutter 自定义 CustomPaint 实现流体液态加载动画
前端·flutter·ui kit
现在立刻马上1 年前
PullDownStretchable 快速实现顶部背景下拉放大
swift·ui kit
IT咖啡馆2 年前
6K star!京东开源,京东风格的轻量级移动端组件库
前端·github·ui kit
西红柿炒牛肉2 年前
前端 | 管理系统UI框架React
前端·ui kit