还是这种基础知识简单轻松😢
布局流程
更新约束、布局、绘制,这三个步骤有十分相近的3个方法,对比着更加容易理解:
// 设置为需要更新约束
setNeedsUpdateConstraints
// 立即更新约束
updateConstraintsIfNeeded
// 自定义更新约束
updateConstraints
// 设置为需要更新布局
setNeedsLayout
// 立即更新布局
layoutIfNeeded
// 自定义更新子视图的布局
layoutSubviews
// 设置为需要更新绘制
setNeedsDisplay
// 自定义绘制
drawRect:
它们3个都有设置为需要更新、自定义更新这两个方法,除了绘制之外,都有立即更新的方法。之所以这样,跟它们的流程有关。
布局并不是在对约束有变化时就立即更新的,而是在 runloop 循环周期的最后检查是否有约束被标记为需要更新,如果有,就会执行更新操作。
更新约束
1. 更新约束里具体做了什么?
我没有找到权威的文档说明这个问题,而且说到这个问题的文章也都含糊不清。所以以下都是我的猜测:
约束是一个系统,一旦其中有一个变化,会引起一连串的约束可能的变化,我觉得这个步骤单单去处理这些约束的关系,就已经很复杂了:
(1)约束之间有冲突,要做决断
(2)有模糊的约束,要做出假设
(3)通过某些数据结构把约束的关系表示出来,方便接下来布局
我觉得这步骤是不包括生成 frame 的,这样更符合分层,但是这样的问题意义不大。
2. 更新约束过程
(1)setNeedsUpdateConstraints - 约束被标记为需要更新:修改约束时自动会把约束标记为需要更新,或者用户根据场景可以自己标记
(2)runloop 开始检查是否有约束需要更新
(3)Auto Layout Engine 内部更新约束
(4)updateConstraints:并同时在合适的时机给我们机会自己更新约束,通过调用 updateConstraints 方法
如果这个约束的更新影响接下来的代码,也可以提前更新:updateConstraintsIfNeeded。
注意:这个方法只是把更新提前了,但是如果没有约束被标记为更新,还是不会执行更新操作的。
3. 更新约束的过程是从下往上的
这个我也不能说出为什么。虽然很多时候,子视图决定了父视图的约束,但是也会有很多反过来的情况。
布局
1. 更新布局的过程
这个过程是从上往下的,先更新父视图的,再更新子视图的。
(1)setNeedsLayout - 布局被标记为需要更新:约束变化时或者我们直接修改 frame 等时,自动会把布局标记为需要更新,或者用户根据场景可以自己标记
(2)runloop 开始检查是否需要更新布局
(3)layoutSubviews:系统更新布局,并同时在合适的时机给我们机会自己更新布局
有文章列举了什么情况下会调用 layoutSubviews,但我觉得这些不需要去记忆,因为这些都是为我们开发者设计的,我们什么时候想要去调用,苹果就会设计成什么时候去调用:
(1)调用addSubView方法时会触发
(2)设置view的frame时会触发,但是需要frame发生变化
(3)滚动scrollView会触发(创建的View的父视图时scrollView,并且进行滚动时)
(4)改变一个UIView大小的时候也会触发父UIView上的layoutSubviews事件
(5)init初始化不会触发layoutSubviews,但是initWithFrame初始化时,rect的值不是zero时也会触发
(6)直接调用[self setNeedsLayout](这个方法不会立即刷新,如果需要立即刷新还需要调用[self layoutIfNeeded])
(7)旋转Screen会触发父UIView上的layoutSubviews事件。
这些事件总结来看就是:
1. 本身的布局发生变化:比如(2)(3)(5)(6)(7),需要说明的是:
(1)[self setNeedsLayout] 本身并不会直接造成调用 layoutSubviews,而是约束确实变化了才会,如果约束只是被标记为需要更新,但是自动布局系统更新完了之后发现,约束没有变化,那当然就不需要更新布局了,当然也就不会调用 layoutSubviews 了
(2)旋转屏幕会不会触发 layoutSubviews 跟上面也是同理,旋转屏幕之所以会触发,是因为父视图布局变了,如果父视图的布局没变,那就不会触发子视图的了
2. 苹果认为你可能也会需要的:比如(1)(4)
(1)当被加入到某个父视图上时,可能会需要做一些修改
(2)当兄弟视图布局变化时,可能会需要做一些修改
绘制
我们的 UI 元素能交给 GPU 的有:颜色,布局,边框,图,我们的控件都是由这些东西组合而成:
(1) label:把字绘制成图,加上背景色等
(2) button:把字绘制成图,加上背景图前景图等
(3) UISwitch:把图形绘制成图
这里面都有个绘制的过程,绘制也就是把矢量图转成图片。
GPU 擅长的是矩阵的处理,因为它有大量的管线,可以同时开工,要求就是不同管线里的数据和代码没有依赖关系。
CPU 更加通用,它计算能力没有 GPU 强悍,但是它能处理依赖关系。
矢量图转成图片,适合用 CPU。所以我们得把这些东西处理好了,再给 GPU 处理。
drawRect:就是给开发者一个机会自定义矢量图,它的调用当然也是在视图的布局发生变化时。
提交给 GPU 显示
交给 GPU 之前,还需要 CPU 解压图片、颜色格式转换、打包数据等。
GPU 收到数据之后,要做顶点处理等一系列的操作。