写在前面
- 什么时候需要考虑页面性能问题
- 如何进行页面性能评估
- 如何具体实施性能优化
PS: 任何提前优化都是魔鬼
图层混合
很多时候,界面都是会出现多个UI控件叠加的情况,如果有透明或者半透明的控件,那么GPU会去计算这些这些layer最终的显示的颜色,也就是我们肉眼所看到的效果。例如一个上层Veiw颜色是绿色RGB(0,255,0),下层又放了一个View颜色是红色RGB(0,0,255),透明度是50%,那么最终显示到我们眼前的颜色是蓝色RGB(0,127.5,127.5)。这个计算过程会消耗一定的GPU资源损耗性能。如果我们把上层的绿色View改为不透明, 那么GPU就不用耗费资源计算,直接显示绿色。在实际开发过程中应该尽量避免图层混合。
调试步骤:
- Command + i,进入Instruments,选择Core Animation,选择设备及调试进程(App)
- 勾选调试选项为
Color Blended Layers
- 开始录制并在真机上查看页面,图层混合区域为红色,无混合区域为绿色
分析结论:
App中图片多不包含透明通道,故图片的图层混合基本上表现优于Label和Button。处理类似图层问题,需要我们自编码时添加以下代码:
label.backgroundColor = [UIColor whiteColor];
label.layer.masksToBounds = YES;
还有两个值得注意的地方,如果label的内容是中文,label实际渲染区域要大于label的size,最外层多了一个sublayer,如果不设置第二行label的边缘外层会出现图层混合的红色,因此需要在label内容是中文的情况下保证label的masksToBounds
属性为YES
是不会发生离屏渲染,下文会讲离屏渲染。UIImageView控件比较特殊,不仅需要自身这个容器是不透明的,并且imageView包含的内容图片也必须是不透明的,如果你自己的图片出现了图层混合红色,先检查是不是自己的代码有问题,如果确认代码没问题,就是图片自身的问题。
光栅化
光栅化是将一个layer预先渲染成位图(bitmap),再加入到缓存中,成功被缓存的layer会标注为绿色,没有成功缓存的会标注为红色,正确使用光栅化可以得到一定程度的性能提升。如需开启光栅化,需要添加如下代码:
view.layer.shouldRasterize = YES;
适用情况:
一般在图像内容不变的情况下才使用光栅化,例如设置阴影耗费资源比较多的静态内容,如果使用光栅化对性能的提升有一定帮助。
非适用情况:
如果内容会经常变动,这个时候不要开启,否则会造成性能的浪费。例如我们在使用tableViewCell中,一般不要用光栅化,因为tableViewCell的绘制非常频繁,内容在不断的变化,如果使用了光栅化,会造成大量的离屏渲染降低性能。
如果你在一个界面中使用了光栅化,刚进去这个页面的所有使用了光栅化的控件layer都会是红色,因为还没有缓存成功,如果上下滑动你会发现,layer变成了绿色。但是如果你滑动幅度较大会发现,新出现的控件会是红色然后变成绿色,因为刚开始这些控件的layer还没有缓存。
调试步骤:
- Command + i,进入Instruments,选择Core Animation,选择设备及调试进程(App)
- 勾选调试选项为
Color Hits Green and Misses Red
- 开始录制并在真机上查看页面,缓存中(缓存未命中)区域为红色,缓存命中区域为绿色
分析结论:
可以发现,app中并没有去手动设置光栅化,所以调试时也并没有发现界面元素图层缓存命中状态,只有系统状态栏那里做了光栅化的处理。在列表滑动时可以在很短的时间内发现状态栏光栅化缓存命中状态的切换。这是因为,系统给光栅化缓存分配了一个固定的大小,因此不能过度使用,如果超出了缓存也会造成离屏渲染;缓存的时间为100ms,因此如果在100ms内没有使用缓存的对象,则会从缓存中清除。
图片颜色拷贝
苹果官方对于Color Copied Images
的解释大概这个意思,如果GPU不支持当前图片的颜色格式,那么就会将图片交给CPU预先进行格式转化,并且这张图片标记为蓝色。那么GPU支持什么格式呢?苹果的GPU只解析32bit的颜色格式,如果使用Color Copied Images去调试发现是蓝色,这个时候就需要联系设计人员了。
知识扩展:
32bit指的是图片颜色深度,用“位”来表示,用来表示显示颜色数量,例如一个图片支持256种颜色,那么就需要256个不同的值来表示不同的颜色,也就是从0到255,二进制表示就是从00000000到11111111,一共需要8位二进制数,所以颜色深度是8。通常32bit色彩中使用三个8bit分别表示R红G绿B蓝,还有一个8bit常用来表示透明度(Alpha)。
调试步骤:
- Command + i,进入Instruments,选择Core Animation,选择设备及调试进程(App)
- 勾选调试选项为
Color Copied Images
- 开始录制并在真机上查看页面,需要CPU进行格式处理的被标记为蓝色
分析结论:
有类似的问题直接找UI美眉吧~
不标准的表面颜色格式
苹果官方文档未对该选项做任何说明,调试过程中发现该选项只对UILabel生效,普通label为灰色,使用到了Attribute String的label为紫色。
调试步骤:
- Command + i,进入Instruments,选择Core Animation,选择设备及调试进程(App)
- 勾选调试选项为
Color Non-Standard Surface Formats
- 开始录制并在真机上查看页面,需要CPU进行格式处理的被标记为蓝色
分析结论:
无
图片对齐
这个选项可以帮助我们查看图片大小是否正确显示。如果image size和imageView size不匹配,image会出现黄色。要尽可能的减少黄色的出现,因为image size与imageView size不匹配,会消耗资源拉伸/压缩图片。
调试步骤:
- Command + i,进入Instruments,选择Core Animation,选择设备及调试进程(App)
- 勾选调试选项为
Color Misaligned Images
- 开始录制并在真机上查看页面,需要CPU进行格式处理的被标记为蓝色
分析结论:
有类似的问题根据应用场景自行处理,在某些情况下我们需要系统来为我们做图片拉伸/压缩的工作,例如打标的背景图片就是做了拉伸,但对于服务图片来说,我们可能需要做图片的裁剪。
离屏渲染
离屏渲染Off-Screen Rendering 指的是GPU在当前屏幕缓冲区以外新开辟一个缓冲区进行渲染操作。还有另外一种屏幕渲染方式-当前屏幕渲染On-Screen Rendering ,指的是GPU的渲染操作是在当前用于显示的屏幕缓冲区中进行。 离屏渲染会先在屏幕外创建新缓冲区,离屏渲染结束后,再从离屏切到当前屏幕, 把离屏的渲染结果显示到当前屏幕上,这个上下文切换的过程是非常消耗性能的,实际开发中尽可能避免离屏渲染。
触发离屏渲染Offscreen rendering的行为:
drawRect:方法
layer.shadow
layer.allowsGroupOpacity or layer.allowsEdgeAntialiasing
layer.shouldRasterize
layer.mask
layer.masksToBounds and layer.cornerRadius
这里有需要注意的是第三条layer.shouldRasterize,其实就是光栅化,光栅化会触发离屏渲染,因此光栅化慎用。设置圆角(layer方式)会触发离屏渲染,如果在某个页面大量使用了圆角,会非常消耗性能造成FPS急剧下降,设置圆角(layer方式)触发离屏渲染要同时满足下面两个条件:
layer.masksToBounds = YES;
layer.cornerRadius = 5.f;
调试步骤:
- Command + i,进入Instruments,选择Core Animation,选择设备及调试进程(App)
- 勾选调试选项为
Color Offscreen-Rendered Yellow
- 开始录制并在真机上查看页面,需要CPU进行格式处理的被标记为蓝色
分析结论:
为了尽可能避免触发离屏渲染,我们可以换其他手段来实现必要的功能:
- 阴影绘制shadow:使用ShadowPath来替代shadowOffset等属性的设置
imageViewLayer.shadowPath = CGPathCreateWithRect(imageRect, NULL); - 利用GraphicsContex生成一张带圆角的图片或者view,或者用边角图片覆盖在需要圆角的视图上。