一.离屏渲染
1. 在屏渲染和离屏渲染
- 在屏渲染是指在当前用于显示的屏幕缓冲区进行GPU渲染操作
- 离屏渲染是指在当前屏幕缓冲区以外新开辟一个缓冲区进行GPU渲染
当我们指定了UI视图的某些属性标记为它在位域合成之前不能用于当前屏幕上面直接显示的时候就会触发离屏渲染,当前屏幕缓冲区以外新开辟一个缓冲区进行GPU渲染。
2.何时触发离屏渲染
- 圆角(和maskToBounds一起使用时)
- 图层蒙版
- 阴影
- 光栅化
3.为何要避免离屏渲染
- 创建新缓冲区
- 上下文切换,当前屏幕缓冲区(On-Screen)和离屏缓冲区(Off-Screen)来回切换,切换上下文付出的代价较大。
- 在进行离屏渲染时会加大GPU的工作时间,使CPU+GPU的的时间大大超过一帧所用的时间,由此导致掉帧卡顿,所以要避免离屏渲染。
二.UITableView流畅性优化
1.避免主线程阻塞
- 尽量在子线程完成对象的创建调整销毁工作,进行预排版,图片解码等操作
2.避免频繁的对象创建
- 对象的创建会发送内存分配、属性调整等。
- 尽量用轻量的对象代替重量的对象。比如CALayer代替UIView。
- 多利用缓存思想,对象创建后缓存起来,需要的时候再拿出来用。合理利用内存开销,减少CPU开销。
3.减少对象的属性赋值操作
- UIView的frame/bounds等属性的赋值操作,会产生比较大的CPU消耗。
4.异步绘制
- UIKit和CoreAnimation相关操作必须在主线程中进行,其它的可以在后台线程异步执行
- 比方说:为一个UIImageView设置image
imageView.image = image;
以上代码必须在主线程进行,但这个image的绘制过程,可以在异步线程做
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
CGContextRef ctx = CGBitmapContextCreate(...);
// 绘图
CGImageRef imgRef = CGBitmapContextCreateImage(ctx);//位图
UIImage *image = [UIImage imageWithCGImage:imgRef];//转成UIImage
dispatch_async(dispatch_get_main_queue(), ^{
//回到主线程
imageView.image = image;//设置imageView的image
});
});
就是尽量把需要显示的内容,在异步线程绘制,绘制好后再通知主线程显示。
5.简化视图结构
GPU在绘制图像前,会把重叠的视图进行混合,视图结构越复杂,这个操作就越耗时,如果存在透明视图,混合过程会更加复杂。
- 尽量避免复杂的图层结构
- 少使用透明的视图
- 不透明的视图,设置opaque = YES
- 把视图异步绘成一张图
6.减少离屏渲染
三.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.然后结束系统的绘制流程.
异步绘制
怎么进行异步绘制呢,其实就是基于系统给我们开的口子layer.delegate,如果遵从或者实现了displayLayer方法,我们就可以进入到异步绘制流程当中,在异步绘制的过程当中
- 就由delegete去负责生成bitmap位图
- 设置改bitmap作为layer.content属性的值