UIViewController的生命周期

当一个控制器的视图第一次加载并显示到屏幕上时,系统会调用这些函数:

alloc

  • 创建对象、分配空间

init 

  • 初始化对象、数据、变量、属性

awakeFromNib

  • 通过 storyboard 或 xib 加载时被触发,所有的 IBOutlet 和 action 已连接

loadView

  • 第一次访问 view 时触发,如果没有使用 storyboard,系统会调用 loadView
  • 用来加载或创建view层级
  • 此时还没有约束、frame

viewDidLoad

  • view层级加载完成后触发
  • 可以在此添加子视图,设置约束、初始化数据、网络请求、注册通知等
  • 但约束刚创建,还未计算frame

viewWillAppear

  • 控制器即将显示在屏幕上
  • 可更新导航栏、状态栏样式、或每次显示前刷新数据或UI
  • Auto Layout还没真正完成,frame还不是最终值

viewWillLayoutSubviews

  • 即将进行布局时触发,此方法会被多次调用
  • 约束即将被解析为frame

viewDidLayoutSubviews

  • 每次布局完成时触发,此方法会被多次调用
  • UIView层级的frame已经计算完成其子视图的frame布局可能会延迟
  • 可以获取frame、contentSize等布局信息,或者调整滚动位置或动画

viewDidAppear

  • 视图已经显示在屏幕上
  • 适合触发一次性操作或动画,不要做布局修改(已经渲染完成,再改布局可能会卡顿)
  • frame已经确定,屏幕渲染完成

viewWillDisappear

  • 视图即将被移除屏幕
  • 可停止动画,保存当前状态,关闭键盘或手势监听
  • 不要释放数据,此时视图仍然存在

viewDidDisappear

  • 视图已经被移出屏幕
  • 停止计时器,清理临时资源
  • 不要访问已经释放的视图

dealloc

  • 控制器对象被销毁
  • 释放init 或 viewDidLoad 中创建的对象,移除通知、KVO

小结

        我们可以发现,在 viewDidLoad 中创建的约束,并不会立马计算出其 frame 值,在viewWillLayoutSubviews 中约束才会被解析为frame,直到 viewDidLayoutSubviews 中也只是UIView 层级的frame计算完毕,但有一些子视图(例如 collectionView 的 cell、SnapKit 动态约束的子视图)会推迟到稍后的布局循环中才计算出 frame,直到 viewDidAppear方法,所有视图的frame才被计算完成。

UIView 层级是指 self.view 和其直接子视图,可安全读取其 frame 

为什么在 viewDidLayoutSubviews 中,其子视图的布局可能会被延迟呢?

1 . CollectionView / TableView 的惰性布局机制

它们只会为当前可见区域生成cell,不可见的部分会在用户滚动到时才触发布局与渲染。

当启用自动尺寸计算(estimatedItemSize / automaticDimension)时:

  • 系统会先用估算值填充布局
  • 等真正要显示时,才触发 AutoLayout 计算真实高度

2 . SnapKit / AutoLayout 的延迟计算机制

设置约束后,UIKit只是 setNeedsLayout() 标记为“需要重新布局”,而不会立即调用 layoutSubviews() ,直到 RunLoop 即将进入休眠前,才会更新布局计算出frame

Layout转化为Frame的过程

在iOS中,每个 UIView 的位置和尺寸由约束 Constraints 决定,例如:

这些约束本质上是线性方程:

所以 AutoLayout 的求解可以看作是一个线性方程组求解问题

Cassandra 布局引擎

iOS 10之后,Apple 引入了Cassandra 引擎,是 AutoLayout 的改进版求解器。

Cassandra 的特点:

  • 高效处理大量约束
  • 支持优先级冲突
  • 内部使用稀疏矩阵存储约束,节省内存和计算量
  • 最终将约束求解结果转化为每个 view 的frame

稀疏矩阵在约束计算中的作用

稀疏矩阵是一种大部分元素为零的矩阵。例如:一个 1000x1000 的矩阵中,可能只有 100 个非零元素,其余 999,900 个元素为零。存储和计算时,稀疏矩阵会忽略零值,仅记录非零元素的位置和值,从而大幅节省资源。

上述的线性方程组 构建成矩阵形式 Ax = b

因为在UI布局中:

  • 每个视图只与少数其他视图有约束关系
  • 大多数约束只涉及 2-4 个变量
  • 矩阵中 99% 以上的元素都是零

因此使用稀疏矩阵求解更好,实际上,iOS中的约束计算都是采用的矩阵计算。

自动布局第三方库 SnapKit

SnapKit 的核心原理是对 NSLayoutConstraint 的封装,它通过链式语法让 AutoLayout 的使用变得非常简单。

在AutoLayout中,我们通常通过创建 NSLayoutConstraint 实例,然后将其设置为active 来添加约束。

而SnapKit 将这一过程抽象成更简单的步骤:

  • 描述约束:通过链式语法描述视图的约束条件。
  • 约束的封装:SnapKit 内部将这些描述转化为 NSLayoutConstraint,并管理这些约束的生命周期。

其优点:

  • 代码更加简洁

  • 类型安全:编译时检查约束组合是否合理,避免运行时崩溃
  • 强大的更新机制

  • 丰富的约束关系

代码实现

SnapKit 通过为 UIView 实例关联一个 Constraints 数组来存储与该视图相关的约束,这样在更新约束时能够方便地找到之前添加的约束并进行更新或移除。

先看一下每个步骤中涉及的类型和方法:

  • ConstraintMaker: 核心类,用于创建和管理约束。
  • ConstraintMakerRelatable:用于表示约束的关系(例如等于、大于等于等)。
  • ConstraintMakerEditable:用于编辑约束的常量和优先级。

核心流程简化理解:

  • 步骤1:获取 DSL 对象

view.snp -> 返回 ConstraintViewDSL(view: self.view)

  • 步骤2:调用 makeConstraints

ConstraintViewDSL.makeConstraints { make in ... }  -> 调用 ConstraintMaker.makeConstraints(item: view, closure: closure)

  • 步骤3:准备约束

prepareConstraints其实是真正的去配置约束,在闭包中配置约束,再获取ConstraintMaker对象的description变量中的constraint,返回Constraint数组,最后再依次active

  • 步骤4:在闭包中配置约束

例如闭包中要执行  make.left.equalToSuperview().offset(20) 

4.1 make.left

即 maker.left → 返回 ConstraintMakerExtendable

4.2 .equalToSuperview()

ConstraintMakerExtendable是ConstraintMakerRelatable(这个类是用于编辑约束的常量以及优先级)的子类

4.3 .offset(20)

设置 constant = 20 ,此时已经记录了常量值,返回本身

  • 步骤5:激活约束

对Constrints数组中逐个激活约束

  • 步骤6:最终步骤

就体现在类Constraint 的active方法,应该根据描述信息创建系统约束NSLayoutConstraint

上述po出的源码截图可能不是实际运行的方法,只是为了展示SnapKit是如何链式进行约束这一流程。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐