Reference:
先进的自动布局工具箱
深入理解 Autolayout 与列表性能
Update Cycle
UIKit/QuartzCore为主线程的RunLoop添加了Observer,监听kCFRunLoopBeforeWaiting(休眠前)和kCFRunLoopExit(退出)事件,在事件的回调函数中会对视图的更新约束、布局和绘制任务进行处理,再汇总处理后的结果,提交给GPU渲染,每一轮的处理就是视图的一个更新周期(update cycle)。
使用了Autolayout的视图,一个更新周期可分为三个步骤:更新约束 (UpdateConstraints) 、布局(LayoutSubviews)和绘制(DrawRect)。这三步依次进行,每一步都依赖前一步的操作,但并不是单向的,比如在布局的过程中更改了自身的约束,就会再次触发更新约束,并再次布局,所以需要注意避免陷入无限循环的逻辑。
视图层级中未使用Autolayout时,可理解为视图的更新周期中没有更新约束的步骤。
UpdateConstraints
当视图的约束发生了变化,或者手动调用-setNeedsUpdateConstraints
将视图标记为需要更新约束之后,视图会被添加到全局容器中,然后在下一个更新周期中更新约束:
- 视图的约束更新方法是
-updateConstraints
,更新约束时会自下而上(从subview到superview)的调用约束更新方法; - ViewController根视图的约束更新方法调用后会调用ViewController的
-updateViewConstraints
方法; -updateConstraints
和-updateViewConstraints
中对视图约束的更新会在本次更新周期中被计算。
Autolayout
在视图更新约束完成之后,系统会将约束条件转换为一个线性规划问题,再基于Cassowary算法进行求解,然后根据计算结果对视图布局,这个过程就是Autolayout。Autolayout的深入分析可以参见文章:深入理解 Autolayout 与列表性能,了解以下几点有助于理解Autolayout:
- 系统中负责Autolaout求解的类是NSISEnginer,一个window上的view复用同一个NSISEnginer对象,这种情况能发挥出Cassowary算法增量更新机制的优势;
- Autolayout增加了约束的更新和求解,自然会带来性能的影响,在iOS12之后,Autolayout完成了优化,性能的消耗已经和手写布局(frame)一样随视图嵌套而呈线性增长,可以放心使用;
- Autolayout的计算和布局都在主线程中完成,并且增加了Text layout时的计算工作,相比而言,Text layout对性能的影响更大;
- Autolayout在计算完成后并不操作视图的frame,
-setFrame:
方法不会被执行,Autolayout是将计算结果作用于视图的Alignment Rect;
更多Autolayout的使用技巧参见:先进的自动布局工具箱
LayoutSubviews
与视图更新约束相似,当视图的层级、center、bounds等布局内容发生了变化,或者手动调用-setNeedsLayout
为视图做了标记之后,视图会被添加到全局容器中,在下一个更新周期中进行布局:
- 视图的布局方法是
-layoutSubviews
,布局过程会自上而下(从superview到subview)调用布局方法; - ViewController根视图的布局方法调用前后会分别调用
-viewWillLayoutSubviews
和-viewDidLayoutSubviews
; - LayoutSubviews系列方法中对视图布局的设置都会在本次更新周期中被使用;
-layoutSubviews
使用时需要调用super的实现;-layoutSubviews
中如果改变了约束,会引起约束更新和布局的迭代,注意不要产生无限循环的情况。
DrawRect
当视图的绘制内容发生了变化(例如改变了背景色),或者手动调用-setNeedsDisplay
/-setNeedsDisplayInRect:
方法标记视图需要重绘之后,视图会被添加到全局容器中,在下一个更新周期中重绘:
- 视图的绘制方法是
-drawRect:
,绘制方法的调用与superview或subview无关; -setNeedsDisplayInRect:
是标记视图局部需要重绘,rect参数会在重绘时传入绘制方法中;- 将View的contentMode设置为
UIViewContentModeRedraw
之后,视图frame的变化也会引起视图的重绘;
LayoutIfNeeded
视图的约束和布局发生变化或手动标记之后,会在下一个更新周期被集中处理,这个过程被叫做Deferred Layout Pass。在开发中有时需要在本轮的Deferred Layout Pass之前知道约束和布局更新的结果,系统提供了-layoutIfNeeded
方法来立刻刷新视图的约束与布局:
-layoutIfNeeded
是同步操作,方法执行完成即可通过视图获得刷新后的布局;- 视图约束与布局发生变化或手动标记之后,调用
-layoutIfNeeded
才会刷新; - 未添加到window上的视图,调用
-layoutIfNeeded
方法时,需要新建NSISEnginer进行计算,性能比添加到window后,通过共用的NSISEnginer计算是差; - 系统还提供了
-systemLayoutSizeFittingSize
方法来立刻获取视图刷新后的布局,它计算时总会新建NSISEnginer,但做了计算优化,且计算完成后不会操作视图的frame; - 已添加到window上的视图,在需要时优先使用
-layoutIfNeeded
,未添加到window上的视图,如果仅仅是为了获取布局计算的结果,优先使用-systemLayoutSizeFittingSize
。