学计算机的那个

不是我觉到、悟到,你给不了我,给了也拿不住;只有我觉到、悟到,才有可能做到,能做到的才是我的.

0%

UIView绘制原理以及异步绘制

前言

由于UIKit框架的线程不安全性,苹果规定UI渲染相关操作要放在主线程来执行,不过CoreGraphic框架是线程安全的。

UIView绘制流程

UIView调用setNeedsDisplay方法后,实际上并没有发生当前视图的绘制工作,而是在之后的某一时机进行绘制工作,为什么会在之后的某一时机进行绘制工作呢?
当UIView调用setNeedDisplay之后,系统会调用view对应layer的 setNeedsDisplay方法,相当于在当前layer上打上了一个脏标记,然后会在当前runloop即将结束的时候调用CALayer的display方法,才会真正的进入当前视图的绘制流程当中,所以视图的绘制时机,是在当前runloop即将结束的时候才会开始。

CALayer的display方法的内部实现,首先会判断layer的delegete是否响应display方法,如果代理不响应就会进入到系统的绘制流程当中,如果响应,实际上就为我们提供了异步绘制的接口,这样就构成了UIView的绘制原理

系统的绘制流程

首先CALayer会在内部创建一个backing store(CGContextRef),我们一般在drawRect中可以通过上下文堆栈当中拿到当前栈顶的context.然后layer判断是否有代理,如果没有代理会调用layer的drawInContext方法,如果实现了代理就会调用delegete的drawLayer:inContext方法,这是在发生在系统内部当中的,然后在合适的时机给予回调方法,也就是View的drawRect方法.可以通过drawRect方法做一些其他的绘制工作.然后无论哪两个分支,都有calayer上传backing store (最终的位图)到CPU.然后结束系统的绘制流程.

何为渲染

渲染就是把我们代码里设置的代码的视图和数据结合,最后绘制成一张图呈现在用户的面前。每秒绘制60张图,用户看着就是流畅的界面呈现,如果不到60帧,那么越少用户看着就会越卡。

利用Runloop解释一下页面的渲染的过程

当我们调用 [UIView setNeedsDisplay] 时,这时会调用当前 View.layer 的 [view.layer setNeedsDisplay]方法。

这等于给当前的 layer 打上了一个脏标记,而此时并没有直接进行绘制工作。而是会到当前的 Runloop 即将休眠,也就是 beforeWaiting 时才会进行绘制工作。

紧接着会调用 [CALayer display],进入到真正绘制的工作。CALayer 层会判断自己的 delegate 有没有实现异步绘制的代理方法 displayer:,这个代理方法是异步绘制的入口,如果没有实现这个方法,那么会继续进行系统绘制的流程,然后绘制结束。

过程可以用下面这张图来解释:

CALayer 内部会创建一个 Backing Store,用来获取图形上下文。接下来会判断这个 layer 是否有 delegate。

如果有的话,会调用 [layer.delegate drawLayer:inContext:],并且会返回给我们 [UIView DrawRect:] 的回调,让我们在系统绘制的基础之上再做一些事情。

如果没有 delegate,那么会调用 [CALayer drawInContext:]。

以上两个分支,最终 CALayer 都会将位图提交到 Backing Store,最后提交给 GPU。

至此绘制的过程结束。

什么是异步绘制

异步绘制,就是可以在子线程把需要绘制的图形,提前在子线程处理好。将准备好的图像数据直接返给主线程使用,这样可以降低主线程的压力。

异步绘制的过程
要通过系统的 [view.delegate displayLayer:] 这个入口来实现异步绘制。

代理负责生成对应的 Bitmap
设置该 Bitmap 为 layer.contents 属性的值。
整个过程可以通过一张图来说明

异步绘制

怎么进行异步绘制呢,其实就是基于系统给我们开的口子layer.delegate,如果遵从或者实现了displayLayer方法,我们就可以进入到异步绘制流程当中,在异步绘制的过程当中

就由delegete去负责生成bitmap位图
设置改bitmap作为layer.content属性的值

通过一副时序图来了解异步绘制的机制和流程

如何实现异步

  1. 开辟子线程

在子线程使用CoreGraphic生成一张图片然后再在主线程将图片设置到View或者Layer

  1. 监听MainRunloop的空闲时间,进行渲染(YYAsyncLayer)

注册一个RunloopObserver,监听MainRunloop在kCFRunLoopCommonModes(包含kCFRunLoopDefaultMode、UITrackingRunLoopMode)下的kCFRunLoopBeforeWaiting(Runloop将要进入休眠)和kCFRunLoopExit(即将退出本次Runloop)的状态,也就是说在一次Runloop空闲时去执行更新显示的操作。

调用链:

对一个包含了YYAsyncLayer的view,比如YYLable重写layoutSubviews方法添加对layer的setNeedsDisplay方法的调用。

这样一个调用链就形成了:用户操作->[view layoutSubviews]->[view.layer setNeedsDisplay]->[layer display]->[layer _displayAsync]异步绘制开始(准确的说是_displayAsync方法的参数为true**的时候开始异步绘制)。

但是这并没有用到RunLoop。所以代码会修改为每次调用layoutSubviews的时候给RunLoop提交一个异步绘制的任务:在runloop空闲时回调执行。

参考

  1. UIView绘制原理,异步绘制
  2. iOS的异步绘制–YYAsyncLayer源码分析
  3. Custom Drawing iOS核心动画高级技巧