iOS核心动画高级技巧 - 7atom
要更快性能,也要作对正确的事情。 ——Stephen R. Coveyspa
在第14章『图像IO』讨论如何高效地载入和显示图像,经过视图来避免可能引发动画帧率降低的性能问题。在最后一章,咱们将着重图层树自己,以发掘最好的性能。code
寄宿图能够经过Core Graphics直接绘制,也能够直接载入一个图片文件并赋值给contents
属性,或事先绘制一个屏幕以外的CGContext
上下文。在以前的两章中咱们讨论了这些场景下的优化。可是除了常见的显式建立寄宿图,你也能够经过如下三种方式建立隐式的:1,使用特性的图层属性。2,特定的视图。3,特定的图层子类。orm
了解这个状况为何发生什么时候发生是很重要的,它可以让你避免引入没必要要的软件绘制行为。
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项目,是否已光栅化图像被频繁地刷新(这样就说明图层并非光栅化的好选择,或则你无心间触发了没必要要的改变致使了重绘行为)。
当图层属性的混合体被指定为在未预合成以前不能直接在屏幕中绘制时,屏幕外渲染就被唤起了。屏幕外渲染并不意味着软件绘制,可是它意味着图层必须在被显示以前在一个屏幕外上下文中被渲染(不论CPU仍是GPU)。图层的如下属性将会触发屏幕外绘制:
圆角(当和maskToBounds
一块儿使用时)
图层蒙板
阴影
屏幕外渲染和咱们启用光栅化时类似,除了它并无像光栅化图层那么消耗大,子图层并无被影响到,并且结果也没有被缓存,因此不会有长期的内存占用。可是,若是太多图层在屏幕外渲染依然会影响到性能。
有时候咱们能够把那些须要屏幕外绘制的图层开启光栅化以做为一个优化方式,前提是这些图层并不会被频繁地重绘。
对于那些须要动画并且要在屏幕外渲染的图层来讲,你能够用CAShapeLayer
,contentsCenter
或者shadowPath
来得到一样的表现并且较少地影响到性能。
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(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]; } @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(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.png"].CGImage; //add it to our view [self.layerView.layer addSublayer:blueLayer]; } @end
使用可伸缩图片的优点在于它能够绘制成任意边框效果而不须要额外的性能消耗。举个例子,可伸缩图片甚至还能够显示出矩形阴影的效果。
在第2章咱们有提到shadowPath
属性。若是图层是一个简单几何图形如矩形或者圆角矩形(假设不包含任何透明部分或者子图层),建立出一个对应形状的阴影路径就比较容易,并且Core Animation绘制这个阴影也至关简单,避免了屏幕外的图层部分的预排版需求。这对性能来讲颇有帮助。
若是你的图层是一个更复杂的图形,生成正确的阴影路径可能就比较难了,这样子的话你能够考虑用绘图软件预先生成一个阴影背景图。
在第12章有提到,GPU每一帧能够绘制的像素有一个最大限制(就是所谓的fill rate),这个状况下能够轻易地绘制整个屏幕的全部像素。可是若是因为重叠图层的关系须要不停地重绘同一区域的话,掉帧就可能发生了。
GPU会放弃绘制那些彻底被其余图层遮挡的像素,可是要计算出一个图层是否被遮挡也是至关复杂而且会消耗处理器资源。一样,合并不一样图层的透明重叠像素(即混合)消耗的资源也是至关客观的。因此为了加速处理进程,不到必须时刻不要使用透明图层。任何状况下,你应该这样作:
给视图的backgroundColor
属性设置一个固定的,不透明的颜色
设置opaque
属性为YES
这样作减小了混合行为(由于编译器知道在图层以后的东西都不会对最终的像素颜色产生影响)而且计算获得了加速,避免了过分绘制行为由于Core Animation能够舍弃全部被彻底遮盖住的图层,而不用每一个像素都去计算一遍。
若是用到了图像,尽可能避免透明除非很是必要。若是图像要显示在一个固定的背景颜色或是固定的背景图以前,你不必相对前景移动,你只须要预填充背景图片就能够避免运行时混色了。
若是是文本的话,一个白色背景的UILabel
(或者其余颜色)会比透明背景要更高效。
最后,明智地使用shouldRasterize
属性,能够将一个固定的图层体系折叠成单张图片,这样就不须要每一帧从新合成了,也就不会有由于子图层之间的混合和过分绘制的性能问题了。
初始化图层,处理图层,打包经过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 - 1)*SPACING, (HEIGHT - 1)*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 - 1; z >= 0; z--) { for (int y = 0; y < HEIGHT; y++) { for (int x = 0; x < WIDTH; x++) { //create layer CALayer *layer = [CALayer layer]; layer.frame = CGRectMake(0, 0, SIZE, SIZE); layer.position = CGPointMake(x*SPACING, y*SPACING); layer.zPosition = -z*SPACING; //set background color layer.backgroundColor = [UIColor colorWithWhite:1-z*(1.0/DEPTH) alpha:1].CGColor; //attach to scroll view [self.scrollView.layer addSublayer:layer]; } } }  //log NSLog(@"displayed: %i", DEPTH*HEIGHT*WIDTH); } @end
本章学习了使用Core Animation图层可能遇到的性能瓶颈,并讨论了如何避免或减少压力。你学习了如何管理包含上千虚拟图层的场景(事实上只建立了几百个)。同时也学习了一些有用的技巧,选择性地选取光栅化或者绘制图层内容在合适的时候从新分配给CPU和GPU。这些就是咱们要讲的关于Core Animation的所有了(至少能够等到苹果发明什么新的玩意儿)。