沒必要要的效率考慮每每是性能問題的萬惡之源。 ——William Allan Wulfgit
在第12章『速度的曲率』咱們學習如何用Instruments來診斷Core Animation性能問題。在構建一個iOS app的時候會遇到不少潛在的性能陷阱,可是在本章咱們將着眼於有關繪製的性能問題。github
術語繪圖一般在Core Animation的上下文中指代軟件繪圖(意即:不禁GPU協助的繪圖)。在iOS中,軟件繪圖一般是由Core Graphics框架完成來完成。可是,在一些必要的狀況下,相比Core Animation和OpenGL,Core Graphics要慢了很多。app
軟件繪圖不只效率低,還會消耗可觀的內存。CALayer
只須要一些與本身相關的內存:只有它的寄宿圖會消耗必定的內存空間。即便直接賦給contents
屬性一張圖片,也不須要增長額外的照片存儲大小。若是相同的一張圖片被多個圖層做爲contents
屬性,那麼他們將會共用同一塊內存,而不是複製內存塊。框架
可是一旦你實現了CALayerDelegate
協議中的-drawLayer:inContext:
方法或者UIView
中的-drawRect:
方法(其實就是前者的包裝方法),圖層就建立了一個繪製上下文,這個上下文須要的大小的內存可從這個算式得出:圖層寬*圖層高*4字節,寬高的單位均爲像素。對於一個在Retina iPad上的全屏圖層來講,這個內存量就是 2048*1526*4字節,至關於12MB內存,圖層每次重繪的時候都須要從新抹掉內存而後從新分配。性能
軟件繪圖的代價昂貴,除非絕對必要,你應該避免重繪你的視圖。提升繪製性能的祕訣就在於儘可能避免去繪製。學習
咱們用Core Graphics來繪圖的一個一般緣由就是隻是用圖片或是圖層效果不能輕易地繪製出矢量圖形。矢量繪圖包含一下這些:this
任意多邊形(不只僅是一個矩形)atom
斜線或曲線spa
文本code
漸變
舉個例子,清單13.1 展現了一個基本的畫線應用。這個應用將用戶的觸摸手勢轉換成一個UIBezierPath
上的點,而後繪製成視圖。咱們在一個UIView
子類DrawingView
中實現了全部的繪製邏輯,這個狀況下咱們沒有用上view controller。可是若是你喜歡你能夠在view controller中實現觸摸事件處理。圖13.1是代碼運行結果。
清單13.1 用Core Graphics實現一個簡單的繪圖應用
#import "DrawingView.h"@interface DrawingView () @property (nonatomic, strong) UIBezierPath *path;@end@implementation DrawingView- (void)awakeFromNib { //create a mutable path self.path = [[UIBezierPath alloc] init]; self.path.lineJoinStyle = kCGLineJoinRound; self.path.lineCapStyle = kCGLineCapRound;  self.path.lineWidth = 5; }- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{ //get the starting point CGPoint point = [[touches anyObject] locationInView:self]; //move the path drawing cursor to the starting point [self.path moveToPoint:point]; }- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{ //get the current point CGPoint point = [[touches anyObject] locationInView:self]; //add a new line segment to our path [self.path addLineToPoint:point]; //redraw the view [self setNeedsDisplay]; }- (void)drawRect:(CGRect)rect { //draw path [[UIColor clearColor] setFill]; [[UIColor redColor] setStroke]; [self.path stroke]; }@end
圖13.1 用Core Graphics作一個簡單的『素描』
這樣實現的問題在於,咱們畫得越多,程序就會越慢。由於每次移動手指的時候都會重繪整個貝塞爾路徑(UIBezierPath
),隨着路徑愈來愈複雜,每次重繪的工做就會增長,直接致使了幀數的降低。看來咱們須要一個更好的方法了。
Core Animation爲這些圖形類型的繪製提供了專門的類,並給他們提供硬件支持(第六章『專有圖層』有詳細提到)。CAShapeLayer
能夠繪製多邊形,直線和曲線。CATextLayer
能夠繪製文本。CAGradientLayer
用來繪製漸變。這些整體上都比Core Graphics更快,同時他們也避免了創造一個寄宿圖。
若是稍微將以前的代碼變更一下,用CAShapeLayer
替代Core Graphics,性能就會獲得提升(見清單13.2).雖然隨着路徑複雜性的增長,繪製性能依然會降低,可是隻有當很是很是浮躁的繪製時纔會感到明顯的幀率差別。
清單13.2 用CAShapeLayer
從新實現繪圖應用
#import "DrawingView.h"#import <QuartzCore/QuartzCore.h>@interface DrawingView () @property (nonatomic, strong) UIBezierPath *path;@end@implementation DrawingView+ (Class)layerClass { //this makes our view create a CAShapeLayer //instead of a CALayer for its backing layer return [CAShapeLayer class]; }- (void)awakeFromNib { //create a mutable path self.path = [[UIBezierPath alloc] init]; //configure the layer CAShapeLayer *shapeLayer = (CAShapeLayer *)self.layer; shapeLayer.strokeColor = [UIColor redColor].CGColor; shapeLayer.fillColor = [UIColor clearColor].CGColor; shapeLayer.lineJoin = kCALineJoinRound; shapeLayer.lineCap = kCALineCapRound; shapeLayer.lineWidth = 5; }- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{ //get the starting point CGPoint point = [[touches anyObject] locationInView:self]; //move the path drawing cursor to the starting point [self.path moveToPoint:point]; }- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{ //get the current point CGPoint point = [[touches anyObject] locationInView:self]; //add a new line segment to our path [self.path addLineToPoint:point]; //update the layer with a copy of the path ((CAShapeLayer *)self.layer).path = self.path.CGPath; }@end