在平时的开发过程中,我们经常会听到离屏渲染这个词,在面试中也会经常被面试官问到,那么在iOS开发中到底什么是离屏渲染?离屏渲染有什么性能问题?离屏渲染是否应该完全禁止呢?
一、iOS渲染流程梳理
iOS开发中,将图像显示到屏幕上有两种方式:
1、正常渲染流程
2、离屏渲染流程
二、离屏渲染的性能问题
2.1 离屏渲染存在的性能问题
1、相比于正常的渲染流程,离屏渲染需要额外创建一个缓冲区,需要多耗费一些空间;
2、触发离屏渲染后,需要先从Frame Buffer切换到Off-Screen Buffer,渲染完毕后再切换回Frame Buffer,这一过程需是比较耗费性能的,因为要来回切换上下文;
3、数据由Off-Screen Buffer取出,再存入Frame Buffer也需要耗费时间,这样增加了掉帧的可能性;
4、离屏缓冲区存在空间限制,即屏幕像素的2.5倍,当大于这一值时便不会触发离屏渲染。
2.2 为何要使用离屏渲染
既然离屏渲染存在这么多的性能问题,为什么依然存在呢?主要有一下两点原因:
1、有些后续经常用到的图层数据,可以先缓存在离屏缓存,用到时直接复用
2、存在一些特殊效果,正常流程无法完成,必须使用离屏渲染,比如圆角、阴影和遮罩、高斯模糊、半透明图层混合等
正常的渲染流程采用油画算法由远及近的渲染图层,当一个图层显示到屏幕上后,帧缓冲区会立即删除这一图层的数据
例如将这张图显示到屏幕上可以分为两步:
1、先绘制黄色背景图层,显示到屏幕上后,删除帧缓冲区中黄色图层的数据
2、再渲染蓝色图层,显示蓝色图层到屏幕后,删除帧缓冲区中蓝色图层数据
如果给图层设置了特殊效果则有可能需要触发离屏渲染,以圆角为例
我们想要是如右图所示的效果,设置圆角后包括子视图也进行圆角裁剪。 但是按照正常流程显示完黄色图层后,在渲染蓝色图层进行圆角设置时(超出时按圆角裁剪,未超出则不需要裁剪),已经找不到黄色图层的数据。因此,需要增加离屏缓冲区,将后续要用到的图层数据先缓存起来,在后续用到时进行渲染显示。
三、离屏渲染的触发及检测
3.1 离屏渲染检测
1、模拟器下检测:Simulator --> Debug --> Color Off-screen rendered,模拟器下只需要设置模拟器一次就可以
2、真机下检测:XCode --> Debug --> View Debugging -->Rendering --> Color Offscreen-Rendered Yellow,真机下每次运行后都要在XCode中设置一下生效
检测结果如果覆盖有黄色图层,则表示产生了离屏渲染,否则没有产生离屏渲染
3.2 离屏渲染触发及建议
1、如上文所述,实现一些特殊效果例如圆角、阴影和遮罩、高斯模糊、半透明图层混合等
2、设置view.layer.shouldRasterize 为 true时,会触发离屏渲染
shouldRasterize 光栅化使用目的: 通过开辟离屏缓冲区缓存图像,以便将来使用,提升性能。但是如果缓存的图像会经常被更改,则开启离屏缓存区反而会降低性能。
因此对于是否开启shouldRasterize有以下建议:
- 如果缓存的图像在之后用不到或很少用到(100ms内用不到),则不需要开启shouldRasterize
- 如果缓存的图像会经常发生变动,比如本身处于动画中,或者像tabeleView的cell的上图片可能经常改变,则不要开启shouldRasterize
- 缓存的图像过大,超过屏幕像素的2.5倍,不会触发离屏渲染,所以开启shouldRasterize也没有效果
四、iOS设置圆角触发离屏渲染原因
我们以UIButton和 UIImageView为例:
//1.按钮存在背景图片 UIButton *btn1 = [UIButton buttonWithType:UIButtonTypeCustom]; btn1.frame = CGRectMake(100, 30, 100, 100); btn1.layer.cornerRadius = 50; [self.view addSubview:btn1];// [btn1 setTitle:@"超出边界了会离屏渲染" forState:UIControlStateNormal];// [btn1 setTitleColor:[UIColor redColor] forState:UIControlStateNormal]; [btn1 setImage:[UIImage imageNamed:@"btn.png"] forState:UIControlStateNormal]; btn1.clipsToBounds = YES; //2.按钮不存在背景图片 UIButton *btn2 = [UIButton buttonWithType:UIButtonTypeCustom]; btn2.frame = CGRectMake(100, 180, 100, 100); btn2.layer.cornerRadius = 50; btn2.backgroundColor = [UIColor blueColor]; [self.view addSubview:btn2]; btn2.clipsToBounds = YES; //3.UIImageView 设置了图片+背景色; UIImageView *img1 = [[UIImageView alloc]init]; img1.frame = CGRectMake(100, 320, 100, 100); img1.backgroundColor = [UIColor blueColor]; [self.view addSubview:img1]; img1.layer.cornerRadius = 50; img1.layer.masksToBounds = YES; img1.image = [UIImage imageNamed:@"btn.png"]; //4.UIImageView 只设置了图片,无背景色; UIImageView *img2 = [[UIImageView alloc]init]; img2.frame = CGRectMake(100, 480, 100, 100); [self.view addSubview:img2]; img2.layer.cornerRadius = 50; img2.layer.masksToBounds = YES; img2.image = [UIImage imageNamed:@"btn.png"];复制代码
这段代码的运行效果如下:
结果显示1和3触发了离屏渲染,2和4未触发离屏渲染。为什么会这样呢?我们先看一下cornerRadius和masksToBounds这几个属性。
cornerRadius用于设置圆角半径
masksToBounds设置超出部分裁剪,仅设置cornerRadius不会对内容进行圆角处理,只有设置 masksToBounds=YES才会对内容进行圆角处理
从运行结果可以看出,虽然设置了cornerRadius和masksToBounds = YES, bt2并没有会触发离屏渲染,可见不是设置了masksToBounds = YES就一定会离屏渲染。
因为bt2只设置了一个背景颜色,只有一个背景图层,直接将这一层渲染到屏幕上就可以了,不需要开辟离屏缓冲区。而bt1设置了一个背景图片,会有一个背景图层和内容图层,所以需要离屏渲染(如果去掉图片,设置title,则title长度超出时,会离屏渲染,title未超出则不会触发)。
在 3 和 4中,img1设置了 图片 + 背景颜色,会有两个图层则产生离屏渲染,img2只设置背景,没有图片,则不会离屏渲染。
由此可见,设置圆角触发离屏渲染的条件是contents有子视图,并设置了masksToBounds = YES。这是一个与的关系,两者必须都满足。