iOS图层性能优化

2,876 阅读9分钟

来自译文 iOS核心动画高级技巧


[toc]

下面是阅读iOS核心动画高级技巧关于图层优化的一些记录。

光栅化

可以解决重叠透明图层的混合失灵问题,也作为绘制复杂图层树结构的优化方法。 开启方法:

layer.shouldRasterize = YES;
layer.rasterizationScale = [UIScreen mainScreen].scale

启用 shouldRasterize属性会将图层绘制到一个屏幕之外的图像。然后这个图像将会被缓存起来并绘制到实际图层的contents和子图层。如果有很多的子图层或者有复杂的效果应用,这样做就会比重绘所有事务的所有帧划得来得多。但是光栅化原始图像需要时间,而且还会消耗额外的内存。当我们使用得当时,光栅化可以提供很大的性能优势,但是一定要避免作用在内容不断变动的图层上,否则它缓存方面的好处就会消失,而且会让性能变的更糟。 如何检测 检测你是否正确地使用了光栅化方式,用Instrument > Core Animation > Color Hits Gree and Misses Red选项,查看是否已光栅化图像被频繁地刷新(这样就说明图层并不是光栅化的好选择,或则你无意间触发了不必要的改变导致了重绘行为)。

离屏渲染

当图层属性的混合体被指定为在未预合成之前不能直接在屏幕中绘制时,屏幕外渲染就被唤起了。屏幕外渲染并不意味着软件绘制,但是它意味着图层必须在被显示之前在一个屏幕外上下文中被渲染(不论CPU还是GPU)。图层的以下属性将会 触发屏幕外绘制:

  • 圆角(当和 maskToBounds 一起使用时)
  • 图层蒙板
  • 阴影

屏幕外渲染和我们启用光栅化时相似,除了它并没有像光栅化图层那么消耗大,子图层并没有被影响到,而且结果也没有被缓存,所以不会有长期的内存占用。但是,如果太多图层在屏幕外渲染依然会影响到性能。 有时候我们可以把那些需要屏幕外绘制的图层开启光栅化以作为一个优化方式,前提是这些图层并不会被频繁地重绘。 对于那些需要动画而且要在屏幕外渲染的图层来说,你可以用 CAShapeLayercontentsCenter 或者 shadowPath 来获得同样的表现而且较少地影响到性能。

CAShapeLayer

cornerRadiusmaskToBounds 独立作用的时候都不会有太大的性能问题, 但是当他俩结合在一起,就触发了屏幕外渲染。有时候你想显示圆角并沿着图层裁 切子图层的时候,你可能会发现你并不需要沿着圆角裁切,这个情况下 用 CAShapeLayer 就可以避免这个问题了。 你想要的只是圆角且沿着矩形边界裁切,同时还不希望引起性能问题。其实你可 以用现成的UIBezierPath的构造器+ bezierPathWithRoundedRect: cornerRadius: 这样做并不会比直接用 cornerRadius更快,但是它避免了性能问题。

CAShapeLayer *blueLayer = [CAShapeLayer layer]; blueLayer.frame = CGRectMake(50, 50, 100, 100); blueLayer.fillColor = [UIColor blueColor].CGColor; blueLayer.path = [UIBezierPath bezierPathWithRoundedRect: CGRectMake(0, 0, 100, 100) cornerRadius:20].CGPath;
 
//add it to our view
[self.layerView.layer addSublayer:blueLayer];

可伸缩图片

另一个创建圆角矩形的方法就是用一个圆形内容图片是提到的contentsCenter属性去创建一个可伸缩图片。理论上来说,这 个应该比用 要快,因为一个可拉伸图片只需要18个三角形(一个图片是由一个3*3网格渲染而成),然而,许多都需要渲染成一个顺滑的曲线。在 实际应用上,二者并没有太大的区别。

contentsCenter 其实是一个CGRect,它定义了一个固定的边框和一个在图层上可拉伸的区域。改变contentsCenter的值并不会影响到寄宿图的显示,除非这个图层的大小改变了,你才看得到效果。

CALayer *blueLayer = [CALayer layer];
blueLayer.frame = CGRectMake(50, 50, 100, 100);
blueLayer.contentsCenter = CGRectMake(0.5, 0.5, 0.0, 0.0);
blueLayer.contentsScale = [UIScreen mainScreen].scale;
blueLayer.contents = (__bridge id)[UIImage imageNamed:@"Circle"];
//add it to our view
[self.layerView.layer addSublayer:blueLayer];

使用可伸缩图片的优势在于它可以绘制成任意边框效果而不需要额外的性能消耗。可伸缩图片甚至还可以显示出矩形阴影的效果。

shadowPath

如果图层是一个简单几何图形如矩形或 者圆角矩形(假设不包含任何透明部分或者子图层),创建出一个对应形状的阴影 路径就比较容易,而且Core Animation绘制这个阴影也相当简单,避免了屏幕外的 图层部分的预排版需求。这对性能来说很有帮助。 如果你的图层是一个更复杂的图形,生成正确的阴影路径可能就比较难了,这样子的话你可以考虑用绘图软件预先生成一个阴影背景图。

混合和过度绘制

GPU每一帧可以绘制的像素有一个最大限制(就是所谓的fillrate),这个情况下可以轻易地绘制整个屏幕的所有像素。但是如果由于重叠图层的关系需要不停地重绘同一区域的话,掉帧就可能发生了。 GPU会放弃绘制那些完全被其他图层遮挡的像素,但是要计算出一个图层是否被遮挡也是相当复杂并且会消耗处理器资源。同样,合并不同图层的透明重叠像素(即混合)消耗的资源也是相当客观的。所以为了加速处理进程,不到必须时刻不要使用透明图层。任何情况下,你应该这样做:

  • 给视图的 属性设置一个固定的,不透明的颜色
  • 设置opaque属性为YES

这样做减少了混合行为(因为编译器知道在图层之后的东西都不会对最终的像素颜色产生影响)并且计算得到了加速,避免了过度绘制行为因为Core Animation可以舍弃所有被完全遮盖住的图层,而不用每个像素都去计算一遍。 如果用到了图像,尽量避免透明除非非常必要。 最后,明智地使用 shouldRasterize 属性,可以将一个固定的图层体系折叠成 单张图片,这样就不需要每一帧重新合成了,也就不会有因为子图层之间的混合和 过度绘制的性能问题了。

#减少图层数量

初始化图层,处理图层,打包通过IPC发给渲染引擎,转化成OpenGL几何图形,这些是一个图层的大致资源开销。事实上,一次性能够在屏幕上显示的最大图层数量也是有限的。 确切的限制数量取决于iOS设备,图层类型,图层内容和属性等。但是总得说来 可以容纳上百或上千个。 在对图层做任何优化之前,你需要确定你不是在创建一些不可见的图层,图层在以下几种情况下回事不可见的:

  • 图层在屏幕边界之外,或是在父图层边界之外。
  • 完全在一个不透明图层之后。
  • 完全透明

不可见的图层尽量延迟创建(需要时在创建)。

对象回收

处理巨大数量的相似视图或图层时还有一个技巧就是回收他们。对象回收在iOS 颇为常见; UITableViewUICollectionView 都有用到还有其他很多例子。 对象回收的基础原则就是你需要创建一个相似对象池。当一个对象的指定实例结束了使命,你把它添加到对象池中。每次当你需要一个实例时,你就从池中取出一个。当且仅当池中为空时再创建一个新的。 这样做的好处在于避免了不断创建和释放对象(相当消耗资源,因为涉及到内存的分配和销毁)而且也不必给相似实例重复赋值。


# Instrument>Core Animation使用介绍 >**Instrument** > **Core Animation** 工具也提供了一系列复选框选项来帮助调试渲染瓶颈:
  • Color Blended Layers - 这个选项基于渲染程度对屏幕中的混合区域进行绿到 红的高亮(也就是多个半透明图层的叠加)。由于重绘的原因,混合对GPU性 能会有影响,同时也是滑动或者动画帧率下降的罪魁祸首之一。
  • Color Hits Green and Misses Red - 当使用 shouldRasterizep 属性的时候, 耗时的图层绘制会被缓存,然后当做一个简单的扁平图片呈现。当缓存再生的时候这个选项就用红色对栅格化图层进行了高亮。如果缓存频繁再生的话,就意味着栅格化可能会有负面的性能影响了。
  • Color Copied Images - 有时候寄宿图片的生成意味着Core Animation被强制生成一些图片,然后发送到渲染服务器,而不是简单的指向原始指针。这个选 项把这些图片渲染成蓝色。复制图片对内存和CPU使用来说都是一项非常昂贵的操作,所以应该尽可能的避免。
  • Color Immediately - 通常Core Animation Instruments以每毫秒10次的频率更 新图层调试颜色。对某些效果来说,这显然太慢了。这个选项就可以用来设置每帧都更新(可能会影响到渲染性能,而且会导致帧率测量不准,所以不要一直都设置它)。
  • Color Misaligned Images - 这里会高亮那些被缩放或者拉伸以及没有正确对齐到像素边界的图片(也就是非整型坐标)。这些中的大多数通常都会导致图片的不正常缩放,如果把一张大图当缩略图显示,或者不正确地模糊图像,那么这个选项将会帮你识别出问题所在。
  • Color Offscreen-Rendered Yellow - 这里会把那些需要离屏渲染的图层高亮 成黄色。这些图层很可能需要用 shadowPath 或者 shouldRasterize 来优化。
  • Color OpenGL Fast Path Blue - 这个选项会对任何直接使用OpenGL绘制的 图层进行高亮。如果仅仅使用UIKit或者Core Animation的API,那么不会有任何效果。如果使用 GLKView 或者 CAEAGLLayer,那如果不显示蓝色块的话就意味着你正在强制CPU渲染额外的纹理,而不是绘制到屏幕。
  • Flash Updated Regions - 这个选项会对重绘的内容高亮成黄色(也就是任何在软件层面使用Core Graphics绘制的图层)。这种绘图的速度很慢。如果频繁发生这种情况的话,这意味着有一个隐藏的bug或者说通过增加缓存或者使 用替代方案会有提升性能的空间。