iOS 的界面渲染,是跟 RunLoop 分不开的,屏幕需要更新时,会调用 CPU 协同 GPU ,将界面数据渲染到屏幕上。。。

界面渲染

原理

同 PC 相似,移动设备也包含屏幕刷新与显卡刷新的概念。iOS 的显示系统,是由 VSync 驱动的,VSync 是用来确定GPU刷新频率和屏幕刷新频率同步的机制。VSync 信号由硬件时钟生成,频率取决于硬件,iPhone 真机上通常是 59.97,有两个缓冲区,即显示缓冲区,简称A、预备缓冲区,简称B,当 VSync 信号到来时,显示器显示 A 中的数据,同时GPU 生成 B 中的数据,并且在显示器显示 A 中数据结束后,将 B 中数据拷贝到 A 中,同时开始绘制下一部分的数据到 B 中。
那么是怎么保证这两个频率同步的呢?
当屏幕刷新率低于显卡刷新率时,GPU 会进行等待,只有 A 中的数据被显示以后,GPU 才会将数据绘制到 B 中,并在下一次循环中拷贝到 A ,从而显示;
当屏幕刷新率高于显卡刷新率时,显卡会暂时显示 A 中的数据,只有等待 B 中数据绘制完整以后,才会将 B 中数据在下一次循环中拷贝到 A。
在 VSync 信号到来后,系统的图形服务会通过 CADisplayLink 等机制通知 App ,App 主线程开始执行计算,包括视图创建、布局计算、图片解码、文本绘制等等,然后 CPU 将计算好的内容提交到 GPU ,GPU 去执行变换、合成、渲染。随后 GPU 将渲染结果提交到后备缓冲区 B 。

渲染时机

那么具体体现在 UIView 中是怎样的呢,在代码操作了 UI 时,比如改变了 Frame ,更新了 UIView/CALayer 层次时,或手动调用了 UIView/CALayer 的 setNeedsLayout 、setNeedsDisplay 方法后,这个 UIView/CALayer 就会被标记为待处理状态,并被提交到一个全局的容器中,当 RunLoop 监听到事件被唤醒时(事件可能有多种,比如触摸,动画,timer,或者CADisplayLink等),CPU 会执行计算,当所有操作结束,RunLoop 即将休眠时,所有的中间状态会被提交到 GPU ,等待下一次 VSync 刷新流程时展示到屏幕上。

UIView 的自动布局流程(转)

大体流程

基本上分为三步:
1.更新约束 (updateConstraints)
2.通过约束关系计算出 center 和 bounds 对 subViews 布局(layoutSubViews)
3.将布局好的 view 显示到屏幕上 (drawRect)

与之相关的方法有如下八个:

更新约束:

1.setNeedsUpdateConstraints:将 view 标记为需要更新约束,并在稍后触发 updateConstraintsIfNeed。
2.updateConstraintsIfNeed:系统会在每个布局节点自动调用此方法。只有约束被标记为需要更新才会调用 updateConstraints。此方法可以手动调用,子类不需要重写此方法。
3.updateConstraints:更新约束的实际方法。

布局:

1.setNeedsLayout:将 view 标记为需要更新布局,并在稍后触发 layoutIfNeed。当 view 的布局改变时会自动调用。
2.layoutIfNeed:系统会在每个布局节点自动调用此方法。只有布局被标记为需要更新时,才会调用 layoutSubViews。
3.layoutSubViews:实际布局。

绘制

1.setNeedsDisplay:将 view 标记为需要重绘,并在下次绘制循环触发 drawRect 。改变布局并不会触发此方法。
2.drawRect:绘制,不能直接调用。

详细过程

当你使用 Auto Layout 去努力创造你想要的布局时,特别是同时伴随高级使用场景和动画时,放下使用场景,去回想布局过程是怎样工作的,将会带来很大的帮助。
相比使用frame布局,Auto Layout 在 Views 可以被显示之前引入了两个额外的处理工序:更新约束,布局视图
每一步都依赖前一步;显示依赖布局,布局依赖约束。

第一步:更新约束(updateConstraints)

更新约束可以被认为是一个测量的过程。他从下到上 bottom-up (from subview to super view)去准备在布局时直接设置 views 的 frame 所需要的信息。
你可用setNeedsUpdateConstraints手动触发这个过程。也可以通过对自身约束系统的改变来自动触发这个过程。即便这样,当你在定制 views 中改变可能影响布局的的约束时通知 Auto Layout 也是有必要的。
说到定制的 views,你可以重写updateConstraints以添加你的 view 在这个阶段需要的局部约束。

第二步:布局(Layout)

布局是从上到下 top-down (from super view to subview)进行的。布局的过程事实上是请求约束系统的结果再通过 center 和 bounds 设置给 views。
你可以通过调用setNeedsLayout触发这个过程。setNeedsLayout 实际上并没有在接下来立马请求布局,而是把你的请求记录下来随后刷新。
通过这种方法你不用担心setNeedsLayout调用太频繁,因为所有的布局请求会被合并到一个布局过程。你可以通过调用layoutIfNeeded来立即触发更新视图层的布局过程,如果你下一步操作依赖于视图最新的布局。
在你的定制 view 中你可以通过重写layoutSubviews去获取布局过程的完全控制。我们将在随后展示这样的用法。

第三部:显示(Display)

最后,显示过程将 views 渲染到屏幕,这个过程是独立的,不管你用没用 Auto Layout。
显示过程从上到下 top-down (from super view to subview) 进行,可以用 setNeedsDisplay 触发,并且会整合所有请求延时重绘。在你的定制 views 重写熟悉的drawRect:方法是你获得 views 在显示过程这个阶段的完全控制的途径。
因为每一步都依赖于前一步,所以显示过程将会触发布局过程(如果有任何布局在等待改变)。同样,布局过程将会触发新更新约束过程(如果约束系统有在等到的改变)。
需要记住的是,这三个过程的调用顺序并不是唯一的。
布局(基于约束 Constraint-based 的)是一个重复的过程。在布局过程中基于上一次布局结果改变约束,将会在下一个布局过程后再次触发约束更新。
这将有助于生成 views 高级定制的布局。但是你也有陷入无限循环的风险,如果每个layoutSubviews方法中调用并唤起了另外一个布局过程的话。

总结

如果想要立即改变约束,需要在setNeedsUpdateConstraints后调用updateConstraintsIfNeeded。
如果想要立即改变布局,需要在setNeedsLayout后调用layoutIfNeeded。

VC 布局时,会从最末级的 view 开始,更新约束,最终调用 VC 的 updateConstraints,然后从 VC 的 viewDidLayoutSubviews 开始,从根到末一级一级的调用子 view 的 layoutSubviews 进行布局,最后从根到末一级一级的调用子 view 的 drawRect 进行绘制。
当我们使用 masonry 创建约束以后,被更改的 view 会被调用 setNeedsLayout 标记为需要更新布局,然后 view 会调用 updateConstraints 更新约束,然后调用 layoutSubviews 进行布局。