iOS性能优化的一个重要方面是视图渲染,在渲染的过程中避免出现图层混合,离屏渲染等问题,从而减少CPU和GPU的性能开销,达到性能优化的目的。
我们在开发调试过程中,可通过Xcode中的视图调试选项来帮助我们快速查找出渲染过程中的问题。
真机调试运行App项目,在Xcode的工具栏中选择,Debug -> View Debugging -> Rendering,其中有9个选项。
若使用的是模拟器,在Simulator工具栏的Debug选栏中也有视图调试的选项。
选项 | 说明 |
---|---|
Color Blended Layers | 图层混合,产生了图层混合视图上会标注为红色,没有发生图层混合视图上会标注为绿色。红色越多,性能越差。 |
Color Hit Green and Misses Red | 光栅化,光栅化会将layer预先渲染成位图bitmap,然后被缓存,缓存未被复用则标注为红色,缓存被复用会标注为绿色。红色越多,性能越差。 |
Color Copied Images | GPU 不支持的颜色格式的图片需要被复制到CPU进行颜色格式转换,因此这样的图片会被标注为蓝色。蓝色越多,性能越差。 |
Color Layer Formats | 图层颜色格式, 暂不清楚。 |
Color Immediately | 立即刷新, 通常以每10毫秒一次的频率更新图层调试颜色。选中这个选项则设置每帧画面都会刷新,在需要加快颜色刷新频率的场景下使用,通常情况下用不到。 |
Color Misaligned Images | 图片错位,这个选项检查图片是否被缩放,以及像素是否对齐。被缩放的图片会被标记为黄色,像素不对齐则会标注为紫色。黄色、紫色越多,性能越差。 |
Color Offscreen-Rendered Yellow | 离屏渲染,产生离屏渲染的图层会标注为黄色。黄色越多,性能越差。 |
Color Compositing Fast-Path Blue | 快速路径,使用 OpenGL 绘制的图层会标注为蓝色。蓝色越多,性能越好。 |
Flash Updated Regions | 重绘区域,进行了重绘的区域会标注成黄色,使用Core Graphics重新绘制会消耗性能,重绘区域越小越少,性能越好。 |
调试这几个选项,我们来具体看看,针对视图渲染有哪些具体问题需要优化。
Color Hit Green and Misses Red(图层混合)
在多个UI视图叠加的情况下,如果有透明或者半透明的控件,那么GPU会根据透明度去计算这些layer叠加在一起最终的显示的颜色。举例,如果顶层VeiwA颜色是红色RGB(255,0,0),透明度是40%,底层ViewB颜色是绿色RGB(0,255,0),那么最终叠加显示出来颜色是RGB(102,153,0)。
计算公式
RGB(混合) = RGB(A) * alpha + RGB(B) * (1-alpha)
这个渲染过程会消耗GPU性能,因此要避免出现图层混合。
- 方法:去除透明度,给View添加alpha为1的背景色,这样在视图叠加时,因为没有了透明度,重叠部分直接按照顶层视图的颜色去显示。
若视图是UIImageView,不仅需要自身这个容器的alpha为1,并且imageView包含的内容图片也是不透明的。
顺便提一下:UIView的opaque属性默认为Yes。
opaque这个属性不是决定视图是否透明,而是决定在视图渲染过程的处理方式。视图是否透明跟 alpha 和 hidden 有直接关系。
如果视图不透明,就设置opaque为Yes,表示图形系统会将视图视为完全不透明,完全不透明视图应该使用完全不透明的内容来填充,该内容的alpha值应该为1.0(自身的alpha和填充颜色的alpha都应该为1.0),这样图形系统将不考虑它与其他视图的混合颜色计算,从而提高性能。若设置为NO,图形系统通常会将视图与其他内容合成,消耗性能。如果View是在scrollView中或者在动画中,对性能的提升更为明显。
有些View的子类使用drawRect:方法绘制自己的内容,那么opaque属性对其无效。
Color Hit Green and Misses Red(光栅化)
这个选项用来检测是否正确使用layer的shouldRasterize属性,该属性默认是NO,若 shouldRasterize为YES则开启光栅化。光栅化是将layer预先渲染成位图(bitmap),然后缓存起来,缓存未被复用则标注为红色,缓存被复用会标注为绿色,红色越多,性能越差。使用光栅化的视图清晰度会降低。
- 对于不是频繁绘制的静态视图,若需要添加阴影、圆角等,进行光栅化处理,会对性能有提升。
- 对于会频繁绘制的视图,如tableViewCell,一般不要使用光栅化,开启光栅化就会产生离屏渲染,因为光栅化的缓存就是当前屏幕缓冲区以外新开辟缓冲区。由于tableViewCell的绘制非常频繁,内容在不断的变化,会不断的产生缓存,造成大量的离屏渲染降低性能。缓存的保留时间为100ms,如果在100ms内没有使用缓存的对象,则会将缓存释放。正因如此,会出现下面的现象。 若在tableViewCell中开启光栅化,刚进入页面时, 所有使用了光栅化的视图layer都标注为红色,因为虽进行了光栅化缓存,但是没有被复用;在滑动tableView时,若滑动幅度比较小,没有新的视图出现,那么光栅化缓存被复用,所有视图的layer都标注为绿色。随着滑动幅度变大,新的视图出现,新视图的光栅化缓存被创建,但是没有被复用,就会被标注为红色,但是光栅化缓存的保留时间为100ms,新视图标注的红色就会变为绿色。
光栅化的使用有利有弊,正确的使用光栅化可以得到一定程度的性能提升。
Color Copied Images (图片被复制)
苹果的GPU只解析32bit的颜色格式,32bit的颜色格式由RGBA(红、绿、蓝、透明度)四个颜色通道组成,每一个颜色通道都占据8bit,取值范围是[0, 255]。
如果某图片是GPU 不支持的颜色格式,那么图片需要被复制到CPU进行颜色格式转换,这样的图片会被标注为蓝色。蓝色越多,性能越差。
Color Layer Formats(图层颜色格式)
这个选项用来解析图层的颜色格式,具体用途暂不清楚。
打开这个选项后,UILabel都会被标注,标注为银白色或橙色,UIImageView的图片内容有部分透明时标注为紫罗兰色,全不透明时标注为橙色;有趣的是,xib中调整UILabel的textColor,当RGB三个颜色通道的色值一致时(如000000、999999、FFFFFF等),UILabel会被标注为银白色,当RGB三个颜色通道的色值不一样时(如999897等),UILabel会被标注为橙色。
Color Immediately(颜色刷新)
这个选项用来调整颜色刷新频率。通常情况是以每10毫秒一次的频率更新图层调试颜色,选中这个选项则设置每帧画面都会刷新,没有了10毫秒的延迟。在需要加快颜色刷新频率的场景下使用,通常情况下用不到。
Color Misaligned Images(图片错位)
这个选项用来检测图片在展示时是否被放大或缩小,以及像素是否对齐。如果image.size和imageView.size不匹配(例如,image的实际大小是50×50,imageView的尺寸大小25×25,或者为200×200),图片在展示时会缩放或者放大图片,会消耗资源,imageView会被标注为黄色,黄色越多,性能越差。避免出现黄色标注,将imageView.size设置成与image.size一样的。
Color Offscreen-Rendered Yellow(离屏渲染)
这个选项用来检测是会把那些离屏渲染的图层显示为黄色。黄色越多,性能越差。
屏幕渲染有 当前屏幕渲染 和 离屏渲染 两种方式。
- 当前屏幕渲染(On-Screen Rendering):GPU的渲染操作是在当前用于显示的屏幕缓冲区中进行。
- 离屏渲染(Off-Screen Rendering):GPU在当前屏幕缓冲区以外新开辟一个缓冲区进行渲染操作。离屏渲染会先在屏幕外创建新缓冲区,离屏渲染结束后,再从离屏切到当前屏幕, 把离屏的渲染结果显示到当前屏幕上,这个上下文切换的过程很消耗性能,开发中应尽可能避免离屏渲染。
常见的会引起离屏渲染的操作:
- shadows(阴影):在设置阴影属性时,一定要指定阴影路径,可以避免离屏渲染。
view.layer.shadowOffset = CGSizeMake(0, 2);
view.layer.shadowOpacity = 1;
view.layer.shadowRadius = 4;
view.layer.shadowPath = CGPathCreateWithRect(view.bounds, nil);
//或者
view.layer.shadowPath =UIBezierPath(view.bounds).CGPath;
shouldRasterize(光栅化):上面有提到过,开启光栅化就会产生离屏渲染,因为光栅化的缓存就是当前屏幕缓冲区以外新开辟缓冲区。虽然会产生离屏渲染,但是不代表就一定会降低性能。若页面初始创建视图时,很多视图需要设置圆角,阴影,遮罩等属性,一下子处理这些需要图层混合的渲染,很消耗性能,但是开启光栅化,相同的效果只被渲染了一次,然后一直在缓存中被复用,减少了整个渲染的频度和时间,从而提升性能。总之,视情况而定,采用最优的方式来优化性能。
-
masks(遮罩):单独设置layer.cornerRadius,或者设置layer.masksToBounds为Yes,并不会引起离屏渲染,但是同时设置这两个属性会引起离屏渲染。
设置圆角:
1.使用带圆角的图片,这样最直接。
2.iOS11之后,通常设置layer.maskedCorners和layer.cornerRadius,再加上背景色可以了,在设置背景色时,UILabel或者UITextView不要设置backgroundColor,而是设置layer.backgroundColor。label.layer.cornerRadius = 10; label.layer.backgroundColor = [UIColor redColor].CGColor;
3.通过CAShapeLayer,将绘制有圆角的layer赋值给layer.mask。该方法会引起离屏渲染。
4.通过混合图层的方式,在需要添加圆角的视图上再叠加一个部分透明的视图,只对圆角部分进行遮挡,叠加后达到有圆角的效果。多一个图层会增加的工作量与离屏渲染相比微不足道。
Color Compositing Fast-Path Blue(快速路径)
这个选项用来检测是否是直接使用 OpenGL 绘制的, 使用 OpenGL 绘制的图层会标注为蓝色。蓝色越多,性能越好。如果仅仅使用 UIKitAPI,那么不会有任何效果。
Flash Updated Regions(重绘区域)
这个选项用来检测是否出现重新绘制,进行了重绘的区域会标注成黄色,使用Core Graphics重新绘制会消耗性能,因此重绘区域越小越少,性能越好。