七、UIView的布局与刷新

Reference:
先进的自动布局工具箱
深入理解 Autolayout 与列表性能

Update Cycle

UIKit/QuartzCore为主线程的RunLoop添加了Observer,监听kCFRunLoopBeforeWaiting(休眠前)和kCFRunLoopExit(退出)事件,在事件的回调函数中会对视图的更新约束、布局和绘制任务进行处理,再汇总处理后的结果,提交给GPU渲染,每一轮的处理就是视图的一个更新周期(update cycle)。

使用了Autolayout的视图,一个更新周期可分为三个步骤:更新约束 (UpdateConstraints) 、布局(LayoutSubviews)和绘制(DrawRect)。这三步依次进行,每一步都依赖前一步的操作,但并不是单向的,比如在布局的过程中更改了自身的约束,就会再次触发更新约束,并再次布局,所以需要注意避免陷入无限循环的逻辑。

视图层级中未使用Autolayout时,可理解为视图的更新周期中没有更新约束的步骤。

UpdateConstraints

当视图的约束发生了变化,或者手动调用-setNeedsUpdateConstraints将视图标记为需要更新约束之后,视图会被添加到全局容器中,然后在下一个更新周期中更新约束:

  1. 视图的约束更新方法是-updateConstraints,更新约束时会自下而上(从subview到superview)的调用约束更新方法;
  2. ViewController根视图的约束更新方法调用后会调用ViewController的-updateViewConstraints方法;
  3. -updateConstraints-updateViewConstraints中对视图约束的更新会在本次更新周期中被计算

Autolayout

在视图更新约束完成之后,系统会将约束条件转换为一个线性规划问题,再基于Cassowary算法进行求解,然后根据计算结果对视图布局,这个过程就是Autolayout。Autolayout的深入分析可以参见文章:深入理解 Autolayout 与列表性能,了解以下几点有助于理解Autolayout:

  1. 系统中负责Autolaout求解的类是NSISEnginer,一个window上的view复用同一个NSISEnginer对象,这种情况能发挥出Cassowary算法增量更新机制的优势;
  2. Autolayout增加了约束的更新和求解,自然会带来性能的影响,在iOS12之后,Autolayout完成了优化,性能的消耗已经和手写布局(frame)一样随视图嵌套而呈线性增长,可以放心使用;
  3. Autolayout的计算和布局都在主线程中完成,并且增加了Text layout时的计算工作,相比而言,Text layout对性能的影响更大;
  4. Autolayout在计算完成后并不操作视图的frame,-setFrame:方法不会被执行,Autolayout是将计算结果作用于视图的Alignment Rect

更多Autolayout的使用技巧参见:先进的自动布局工具箱

LayoutSubviews

与视图更新约束相似,当视图的层级、center、bounds等布局内容发生了变化,或者手动调用-setNeedsLayout为视图做了标记之后,视图会被添加到全局容器中,在下一个更新周期中进行布局:

  1. 视图的布局方法是-layoutSubviews,布局过程会自上而下(从superview到subview)调用布局方法;
  2. ViewController根视图的布局方法调用前后会分别调用-viewWillLayoutSubviews-viewDidLayoutSubviews
  3. LayoutSubviews系列方法中对视图布局的设置都会在本次更新周期中被使用
  4. -layoutSubviews使用时需要调用super的实现;
  5. -layoutSubviews中如果改变了约束,会引起约束更新和布局的迭代,注意不要产生无限循环的情况。

DrawRect

当视图的绘制内容发生了变化(例如改变了背景色),或者手动调用-setNeedsDisplay/-setNeedsDisplayInRect:方法标记视图需要重绘之后,视图会被添加到全局容器中,在下一个更新周期中重绘:

  1. 视图的绘制方法是-drawRect:,绘制方法的调用与superview或subview无关;
  2. -setNeedsDisplayInRect:是标记视图局部需要重绘,rect参数会在重绘时传入绘制方法中;
  3. 将View的contentMode设置为UIViewContentModeRedraw之后,视图frame的变化也会引起视图的重绘;

LayoutIfNeeded

视图的约束和布局发生变化或手动标记之后,会在下一个更新周期被集中处理,这个过程被叫做Deferred Layout Pass。在开发中有时需要在本轮的Deferred Layout Pass之前知道约束和布局更新的结果,系统提供了-layoutIfNeeded方法来立刻刷新视图的约束与布局:

  1. -layoutIfNeeded是同步操作,方法执行完成即可通过视图获得刷新后的布局;
  2. 视图约束与布局发生变化或手动标记之后,调用-layoutIfNeeded才会刷新;
  3. 未添加到window上的视图,调用-layoutIfNeeded方法时,需要新建NSISEnginer进行计算,性能比添加到window后,通过共用的NSISEnginer计算是差;
  4. 系统还提供了-systemLayoutSizeFittingSize方法来立刻获取视图刷新后的布局,它计算时总会新建NSISEnginer,但做了计算优化,且计算完成后不会操作视图的frame;
  5. 已添加到window上的视图,在需要时优先使用-layoutIfNeeded,未添加到window上的视图,如果仅仅是为了获取布局计算的结果,优先使用-systemLayoutSizeFittingSize
-------------This article is over, thank you for reading -------------