iOS核心动画高级技巧 - 8
15. 图层性能
图层性能
要更快性能,也要做对正确的事情。 ——Stephen R. Covey
在第14章『图像IO』讨论如何高效地载入和显示图像,通过视图来避免可能引起动画帧率下降的性能问题。在最后一章,我们将着重图层树本身,以发掘最好的性能。
15.1 隐式绘制
隐式绘制
寄宿图可以通过Core Graphics直接绘制,也可以直接载入一个图片文件并赋值给contents属性,或事先绘制一个屏幕之外的CGContext上下文。在之前的两章中我们讨论了这些场景下的优化。但是除了常见的显式创建寄宿图,你也可以通过以下三种方式创建隐式的:1,使用特性的图层属性。2,特定的视图。3,特定的图层子类。
了解这个情况为什么发生何时发生是很重要的,它能够让你避免引入不必要的软件绘制行为。
文本
CATextLayer和UILabel都是直接将文本绘制在图层的寄宿图中。事实上这两种方式用了完全不同的渲染方式:在iOS 6及之前,UILabel用WebKit的HTML渲染引擎来绘制文本,而CATextLayer用的是Core Text.后者渲染更迅速,所以在所有需要绘制大量文本的情形下都优先使用它吧。但是这两种方法都用了软件的方式绘制,因此他们实际上要比硬件加速合成方式要慢。
一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这是一个我的iOS交流群:1012951431, 分享BAT,阿里面试题、面试经验,讨论技术, 大家一起交流学习成长!希望帮助开发者少走弯路。
不论如何,尽可能地避免改变那些包含文本的视图的frame,因为这样做的话文本就需要重绘。例如,如果你想在图层的角落里显示一段静态的文本,但是这个图层经常改动,你就应该把文本放在一个子图层中。
光栅化
在第四章『视觉效果』中我们提到了CALayer的shouldRasterize属性,它可以解决重叠透明图层的混合失灵问题。同样在第12章『速度的曲调』中,它也是作为绘制复杂图层树结构的优化方法。
启用shouldRasterize属性会将图层绘制到一个屏幕之外的图像。然后这个图像将会被缓存起来并绘制到实际图层的contents和子图层。如果有很多的子图层或者有复杂的效果应用,这样做就会比重绘所有事务的所有帧划得来得多。但是光栅化原始图像需要时间,而且还会消耗额外的内存。
当我们使用得当时,光栅化可以提供很大的性能优势(如你在第12章所见),但是一定要避免作用在内容不断变动的图层上,否则它缓存方面的好处就会消失,而且会让性能变的更糟。
为了检测你是否正确地使用了光栅化方式,用Instrument查看一下Color Hits Green和Misses Red项目,是否已光栅化图像被频繁地刷新(这样就说明图层并不是光栅化的好选择,或则你无意间触发了不必要的改变导致了重绘行为)。
15.2 离屏渲染
离屏渲染
当图层属性的混合体被指定为在未预合成之前不能直接在屏幕中绘制时,屏幕外渲染就被唤起了。屏幕外渲染并不意味着软件绘制,但是它意味着图层必须在被显示之前在一个屏幕外上下文中被渲染(不论CPU还是GPU)。图层的以下属性将会触发屏幕外绘制:
圆角(当和
maskToBounds一起使用时)图层蒙板
阴影
屏幕外渲染和我们启用光栅化时相似,除了它并没有像光栅化图层那么消耗大,子图层并没有被影响到,而且结果也没有被缓存,所以不会有长期的内存占用。但是,如果太多图层在屏幕外渲染依然会影响到性能。
有时候我们可以把那些需要屏幕外绘制的图层开启光栅化以作为一个优化方式,前提是这些图层并不会被频繁地重绘。
对于那些需要动画而且要在屏幕外渲染的图层来说,你可以用CAShapeLayer,contentsCenter或者shadowPath来获得同样的表现而且较少地影响到性能。
CAShapeLayer
cornerRadius和maskToBounds独立作用的时候都不会有太大的性能问题,但是当他俩结合在一起,就触发了屏幕外渲染。有时候你想显示圆角并沿着图层裁切子图层的时候,你可能会发现你并不需要沿着圆角裁切,这个情况下用CAShapeLayer就可以避免这个问题了。
你想要的只是圆角且沿着矩形边界裁切,同时还不希望引起性能问题。其实你可以用现成的UIBezierPath的构造器+bezierPathWithRoundedRect:cornerRadius:(见清单15.1).这样做并不会比直接用cornerRadius更快,但是它避免了性能问题。
清单15.1 用CAShapeLayer画一个圆角矩形
#import "ViewController.h"
#import @interface ViewController () @property (nonatomic, weak) IBOutlet UIView *layerView; @end @implementation ViewController - (void)viewDidLoad
{
[super viewDidLoad]; //create shape layer
CAShapeLayer *blueLayer = [CAShapeLayer layer];
blueLayer.frame = CGRectMake(, , , );
blueLayer.fillColor = [UIColor blueColor].CGColor;
blueLayer.path = [UIBezierPath bezierPathWithRoundedRect:
CGRectMake(, , , ) cornerRadius:].CGPath;

//add it to our view
[self.layerView.layer addSublayer:blueLayer];
}
@end
可伸缩图片
另一个创建圆角矩形的方法就是用一个圆形内容图片并结合第二章『寄宿图』提到的contensCenter属性去创建一个可伸缩图片(见清单15.2).理论上来说,这个应该比用CAShapeLayer要快,因为一个可拉伸图片只需要18个三角形(一个图片是由一个3*3网格渲染而成),然而,许多都需要渲染成一个顺滑的曲线。在实际应用上,二者并没有太大的区别。
清单15.2 用可伸缩图片绘制圆角矩形
@implementation ViewController - (void)viewDidLoad
{
[super viewDidLoad]; //create layer
CALayer *blueLayer = [CALayer layer];
blueLayer.frame = CGRectMake(, , , );
blueLayer.contentsCenter = CGRectMake(0.5, 0.5, 0.0, 0.0);
blueLayer.contentsScale = [UIScreen mainScreen].scale;
blueLayer.contents = (__bridge id)[UIImage imageNamed:@"Circle.png"].CGImage;
//add it to our view
[self.layerView.layer addSublayer:blueLayer];
}
@end
使用可伸缩图片的优势在于它可以绘制成任意边框效果而不需要额外的性能消耗。举个例子,可伸缩图片甚至还可以显示出矩形阴影的效果。
shadowPath
在第2章我们有提到shadowPath属性。如果图层是一个简单几何图形如矩形或者圆角矩形(假设不包含任何透明部分或者子图层),创建出一个对应形状的阴影路径就比较容易,而且Core Animation绘制这个阴影也相当简单,避免了屏幕外的图层部分的预排版需求。这对性能来说很有帮助。
如果你的图层是一个更复杂的图形,生成正确的阴影路径可能就比较难了,这样子的话你可以考虑用绘图软件预先生成一个阴影背景图。
15.3 混合和过度绘制
混合和过度绘制
在第12章有提到,GPU每一帧可以绘制的像素有一个最大限制(就是所谓的fill rate),这个情况下可以轻易地绘制整个屏幕的所有像素。但是如果由于重叠图层的关系需要不停地重绘同一区域的话,掉帧就可能发生了。
GPU会放弃绘制那些完全被其他图层遮挡的像素,但是要计算出一个图层是否被遮挡也是相当复杂并且会消耗处理器资源。同样,合并不同图层的透明重叠像素(即混合)消耗的资源也是相当客观的。所以为了加速处理进程,不到必须时刻不要使用透明图层。任何情况下,你应该这样做:
给视图的
backgroundColor属性设置一个固定的,不透明的颜色设置
opaque属性为YES
这样做减少了混合行为(因为编译器知道在图层之后的东西都不会对最终的像素颜色产生影响)并且计算得到了加速,避免了过度绘制行为因为Core Animation可以舍弃所有被完全遮盖住的图层,而不用每个像素都去计算一遍。
如果用到了图像,尽量避免透明除非非常必要。如果图像要显示在一个固定的背景颜色或是固定的背景图之前,你没必要相对前景移动,你只需要预填充背景图片就可以避免运行时混色了。
如果是文本的话,一个白色背景的UILabel(或者其他颜色)会比透明背景要更高效。
最后,明智地使用shouldRasterize属性,可以将一个固定的图层体系折叠成单张图片,这样就不需要每一帧重新合成了,也就不会有因为子图层之间的混合和过度绘制的性能问题了。
15.4 减少图层数量
减少图层数量
初始化图层,处理图层,打包通过IPC发给渲染引擎,转化成OpenGL几何图形,这些是一个图层的大致资源开销。事实上,一次性能够在屏幕上显示的最大图层数量也是有限的。
确切的限制数量取决于iOS设备,图层类型,图层内容和属性等。但是总得说来可以容纳上百或上千个,下面我们将演示即使图层本身并没有做什么也会遇到的性能问题。
裁切
在对图层做任何优化之前,你需要确定你不是在创建一些不可见的图层,图层在以下几种情况下回事不可见的:
图层在屏幕边界之外,或是在父图层边界之外。
完全在一个不透明图层之后。
完全透明
Core Animation非常擅长处理对视觉效果无意义的图层。但是经常性地,你自己的代码会比Core Animation更早地想知道一个图层是否是有用的。理想状况下,在图层对象在创建之前就想知道,以避免创建和配置不必要图层的额外工作。
举个例子。清单15.3 的代码展示了一个简单的滚动3D图层矩阵。这看上去很酷,尤其是图层在移动的时候(见图15.1),但是绘制他们并不是很麻烦,因为这些图层就是一些简单的矩形色块。
清单15.3 绘制3D图层矩阵
#import "ViewController.h"
#import #define WIDTH 10
#define HEIGHT 10
#define DEPTH 10
#define SIZE 100
#define SPACING 150
#define CAMERA_DISTANCE 500 @interface ViewController ()

@property (nonatomic, strong) IBOutlet UIScrollView *scrollView; @end @implementation ViewController - (void)viewDidLoad
{
[super viewDidLoad]; //set content size
self.scrollView.contentSize = CGSizeMake((WIDTH - )*SPACING, (HEIGHT - )*SPACING); //set up perspective transform
CATransform3D transform = CATransform3DIdentity;
transform.m34 = -1.0 / CAMERA_DISTANCE;
self.scrollView.layer.sublayerTransform = transform; //create layers
for (int z = DEPTH - ; z >= ; z--) {
for (int y = ; y < HEIGHT; y++) {
for (int x = ; x < WIDTH; x++) {
//create layer
CALayer *layer = [CALayer layer];
layer.frame = CGRectMake(, , SIZE, SIZE);
layer.position = CGPointMake(x*SPACING, y*SPACING);
layer.zPosition = -z*SPACING;
//set background color
layer.backgroundColor = [UIColor colorWithWhite:-z*(1.0/DEPTH) alpha:].CGColor;
//attach to scroll view
[self.scrollView.layer addSublayer:layer];
}
}
}

//log
NSLog(@"displayed: %i", DEPTH*HEIGHT*WIDTH);
}
@end
15.5 总结
总结
本章学习了使用Core Animation图层可能遇到的性能瓶颈,并讨论了如何避免或减小压力。你学习了如何管理包含上千虚拟图层的场景(事实上只创建了几百个)。同时也学习了一些有用的技巧,选择性地选取光栅化或者绘制图层内容在合适的时候重新分配给CPU和GPU。这些就是我们要讲的关于Core Animation的全部了(至少可以等到苹果发明什么新的玩意儿)。
iOS核心动画高级技巧 - 8的更多相关文章
- iOS核心动画高级技巧之核心动画(三)
iOS核心动画高级技巧之CALayer(一) iOS核心动画高级技巧之图层变换和专用图层(二)iOS核心动画高级技巧之核心动画(三)iOS核心动画高级技巧之性能(四)iOS核心动画高级技巧之动画总结( ...
- iOS核心动画高级技巧之图层变换和专用图层(二)
iOS核心动画高级技巧之CALayer(一) iOS核心动画高级技巧之图层变换和专用图层(二)iOS核心动画高级技巧之核心动画(三)iOS核心动画高级技巧之性能(四)iOS核心动画高级技巧之动画总结( ...
- iOS核心动画高级技巧之CALayer(一)
iOS核心动画高级技巧之CALayer(一) iOS核心动画高级技巧之图层变换和专用图层(二)iOS核心动画高级技巧之核心动画(三)iOS核心动画高级技巧之性能(四)iOS核心动画高级技巧之动画总结( ...
- iOS核心动画高级技巧 - 7
13. 高效绘图 高效绘图 不必要的效率考虑往往是性能问题的万恶之源. ——William Allan Wulf 在第12章『速度的曲率』我们学习如何用Instruments来诊断Core Anima ...
- iOS核心动画高级技巧 - 6
11. 基于定时器的动画 基于定时器的动画 我可以指导你,但是你必须按照我说的做. -- 骇客帝国 在第10章“缓冲”中,我们研究了CAMediaTimingFunction,它是一个通过控制动画缓冲 ...
- iOS核心动画高级技巧-2
3. 图层几何学 图层几何学 不熟悉几何学的人就不要来这里了 --柏拉图学院入口的签名 在第二章里面,我们介绍了图层背后的图片,和一些控制图层坐标和旋转的属性.在这一章中,我们将要看一看图层内部是如何 ...
- iOS核心动画高级技巧-1
1. 图层树 图层的树状结构 巨妖有图层,洋葱也有图层,你有吗?我们都有图层 -- 史莱克 Core Animation其实是一个令人误解的命名.你可能认为它只是用来做动画的,但实际上它是从一个叫做L ...
- iOS核心动画高级技巧 - 3
7. 隐式动画 隐式动画 按照我的意思去做,而不是我说的. -- 埃德娜,辛普森 我们在第一部分讨论了Core Animation除了动画之外可以做到的任何事情.但是动画是Core Animation ...
- iOS核心动画高级技巧-5
9. 图层时间 图层时间 时间和空间最大的区别在于,时间不能被复用 -- 弗斯特梅里克 在上面两章中,我们探讨了可以用CAAnimation和它的子类实现的多种图层动画.动画的发生是需要持续一段时间的 ...
随机推荐
- python 比较运算符和逻辑运算符
<1> 比较(即关系)运算符 python中的比较运算符如下表 运算符 描述 示例 == 检查两个操作数的值是否相等,如果是则条件变为真. 如a=3,b=3则(a == b) 为 true ...
- vim命令整理
最近使用vim比较多,整理一下!
- mybatis xml中是sql语句报错: Error creating document instance. Cause: org.xml.sax.SAXParseException: The
最近项目折腾了老半天,找了资料才知道是这么回事... 因为语句中有一个小于号“<”,在XML中,会被当成一个页面元素来解析,不会处理为mysql的SQL语句的组成部分,修改如下: 1.在xml的 ...
- decode(条件,值1,返回值1,值2,返回值2,...值n,返回值n,缺省值)
decode(条件,值1,返回值1,值2,返回值2,...值n,返回值n,缺省值) 该函数的含义如下: IF 条件=值1 THEN RETURN(翻译值1) ELSIF 条件=值2 THEN RETU ...
- qt mvc3
前面两节讲的model是一维的,这次开始二维的也就是我们常说的Table,相对与list,我们多了一个列的概念. 下面讲解一个例子.我先说明一下我们这个例子,在程序目录下,我们有一个文本文件,其中存放 ...
- redis之为什么redis是单线程?
官方FAQ表示,因为Redis是基于内存的操作,CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存的大小或者网络带宽.既然单线程容易实现,而且CPU不会成为瓶颈,那就顺理成章地采用单线程的 ...
- mac 不限速下载大文件
打开终端后,需要在终端处键入一下代码: 「注意:此处代码不完整, 完整版请 关注微信公众号:「100秒分享」,回复编号「334」获取」 cd ~/Downloads && git cl ...
- Macbook上打开多个终端的方法
方法有如下两种 1.首先是的焦点在终端上(使用鼠标点击下终端).然后使用快捷键command+N 即可得到另一个终端界面. (如果现在光标的焦点在浏览器上,那么就是新打开一个浏览器窗口) 2.同样,光 ...
- windows系统下Jenkins 持续集成安装使用
先要下载安装Tomcat,基于Java的web项目可以通过Tomcat运行.下载Jenkins,注意要安装在Tomcat的webapps目录下.安装完Jenkins会自动通过浏览器打开http://l ...
- 使用redis-py的两个类Redis和StrictRedis时遇到的坑
使用redis-py的两个类Redis和StrictRedis时遇到的坑 前言: 今天产品经理说,有几个队列排序的功能不能用了.对比了下以前的代码查到了一个原因,这个比较的坑,总结起来也是自己没好好看 ...