15管钳、圖層性能

圖層性能

要更快性能户矢,也要做對(duì)正確的事情玲献。
——Stephen R. Covey

在第14章『圖像IO』討論如何高效地載入和顯示圖像,通過視圖來避免可能引起動(dòng)畫幀率下降的性能問題。在最后一章捌年,我們將著重圖層樹本身瓢娜,以發(fā)掘最好的性能。

隱式繪制

寄宿圖可以通過Core Graphics直接繪制延窜,也可以直接載入一個(gè)圖片文件并賦值給contents屬性恋腕,或事先繪制一個(gè)屏幕之外的CGContext上下文抹锄。在之前的兩章中我們討論了這些場(chǎng)景下的優(yōu)化逆瑞。但是除了常見的顯式創(chuàng)建寄宿圖,你也可以通過以下三種方式創(chuàng)建隱式的:1伙单,使用特性的圖層屬性获高。2,特定的視圖吻育。3念秧,特定的圖層子類。

了解這個(gè)情況為什么發(fā)生何時(shí)發(fā)生是很重要的布疼,它能夠讓你避免引入不必要的軟件繪制行為摊趾。

文本

CATextLayerUILabel都是直接將文本繪制在圖層的寄宿圖中。事實(shí)上這兩種方式用了完全不同的渲染方式:在iOS 6及之前游两,UILabel用WebKit的HTML渲染引擎來繪制文本砾层,而CATextLayer用的是Core Text.后者渲染更迅速,所以在所有需要繪制大量文本的情形下都優(yōu)先使用它吧贱案。但是這兩種方法都用了軟件的方式繪制肛炮,因此他們實(shí)際上要比硬件加速合成方式要慢。

不論如何宝踪,盡可能地避免改變那些包含文本的視圖的frame侨糟,因?yàn)檫@樣做的話文本就需要重繪。例如瘩燥,如果你想在圖層的角落里顯示一段靜態(tài)的文本秕重,但是這個(gè)圖層經(jīng)常改動(dòng),你就應(yīng)該把文本放在一個(gè)子圖層中厉膀。

光柵化

在第四章『視覺效果』中我們提到了CALayershouldRasterize屬性溶耘,它可以解決重疊透明圖層的混合失靈問題。同樣在第12章『速度的曲調(diào)』中站蝠,它也是作為繪制復(fù)雜圖層樹結(jié)構(gòu)的優(yōu)化方法汰具。

啟用shouldRasterize屬性會(huì)將圖層繪制到一個(gè)屏幕之外的圖像。然后這個(gè)圖像將會(huì)被緩存起來并繪制到實(shí)際圖層的contents和子圖層菱魔。如果有很多的子圖層或者有復(fù)雜的效果應(yīng)用留荔,這樣做就會(huì)比重繪所有事務(wù)的所有幀劃得來得多。但是光柵化原始圖像需要時(shí)間,而且還會(huì)消耗額外的內(nèi)存聚蝶。

當(dāng)我們使用得當(dāng)時(shí)杰妓,光柵化可以提供很大的性能優(yōu)勢(shì)(如你在第12章所見),但是一定要避免作用在內(nèi)容不斷變動(dòng)的圖層上碘勉,否則它緩存方面的好處就會(huì)消失巷挥,而且會(huì)讓性能變的更糟。

為了檢測(cè)你是否正確地使用了光柵化方式验靡,用Instrument查看一下Color Hits Green和Misses Red項(xiàng)目倍宾,是否已光柵化圖像被頻繁地刷新(這樣就說明圖層并不是光柵化的好選擇,或則你無意間觸發(fā)了不必要的改變導(dǎo)致了重繪行為)胜嗓。

離屏渲染

Offscreen rendering does not necessarily imply software drawing, but it means that the layer must first be rendered (either by the CPU or GPU) into an offscreen context before being displayed. The layer attributes that trigger offscreen rendering are as follows:

當(dāng)圖層屬性的混合體被指定為在未預(yù)合成之前不能直接在屏幕中繪制時(shí)高职,屏幕外渲染就被喚起了。屏幕外渲染并不意味著軟件繪制辞州,但是它意味著圖層必須在被顯示之前在一個(gè)屏幕外上下文中被渲染(不論CPU還是GPU)怔锌。圖層的以下屬性將會(huì)觸發(fā)屏幕外繪制:

  • 圓角(當(dāng)和maskToBounds一起使用時(shí))
  • 圖層蒙板
  • 陰影

屏幕外渲染和我們啟用光柵化時(shí)相似,除了它并沒有像光柵化圖層那么消耗大变过,子圖層并沒有被影響到埃元,而且結(jié)果也沒有被緩存,所以不會(huì)有長(zhǎng)期的內(nèi)存占用媚狰。但是岛杀,如果太多圖層在屏幕外渲染依然會(huì)影響到性能。

有時(shí)候我們可以把那些需要屏幕外繪制的圖層開啟光柵化以作為一個(gè)優(yōu)化方式哈雏,前提是這些圖層并不會(huì)被頻繁地重繪楞件。

對(duì)于那些需要?jiǎng)赢嫸乙谄聊煌怃秩镜膱D層來說,你可以用CAShapeLayercontentsCenter或者shadowPath來獲得同樣的表現(xiàn)而且較少地影響到性能。

CAShapeLayer

cornerRadiusmaskToBounds獨(dú)立作用的時(shí)候都不會(huì)有太大的性能問題丘损,但是當(dāng)他倆結(jié)合在一起,就觸發(fā)了屏幕外渲染黄伊。有時(shí)候你想顯示圓角并沿著圖層裁切子圖層的時(shí)候,你可能會(huì)發(fā)現(xiàn)你并不需要沿著圓角裁切派殷,這個(gè)情況下用CAShapeLayer就可以避免這個(gè)問題了还最。

你想要的只是圓角且沿著矩形邊界裁切,同時(shí)還不希望引起性能問題毡惜。其實(shí)你可以用現(xiàn)成的UIBezierPath的構(gòu)造器+bezierPathWithRoundedRect:cornerRadius:(見清單15.1).這樣做并不會(huì)比直接用cornerRadius更快拓轻,但是它避免了性能問題。

清單15.1 用CAShapeLayer畫一個(gè)圓角矩形

#import "ViewController.h"
#import <QuartzCore/QuartzCore.h>

@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

可伸縮圖片

另一個(gè)創(chuàng)建圓角矩形的方法就是用一個(gè)圓形內(nèi)容圖片并結(jié)合第二章『寄宿圖』提到的contensCenter屬性去創(chuàng)建一個(gè)可伸縮圖片(見清單15.2).理論上來說经伙,這個(gè)應(yīng)該比用CAShapeLayer要快扶叉,因?yàn)橐粋€(gè)可拉伸圖片只需要18個(gè)三角形(一個(gè)圖片是由一個(gè)3*3網(wǎng)格渲染而成),然而,許多都需要渲染成一個(gè)順滑的曲線枣氧。在實(shí)際應(yīng)用上溢十,二者并沒有太大的區(qū)別。

清單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

使用可伸縮圖片的優(yōu)勢(shì)在于它可以繪制成任意邊框效果而不需要額外的性能消耗达吞。舉個(gè)例子张弛,可伸縮圖片甚至還可以顯示出矩形陰影的效果。

shadowPath

在第2章我們有提到shadowPath屬性酪劫。如果圖層是一個(gè)簡(jiǎn)單幾何圖形如矩形或者圓角矩形(假設(shè)不包含任何透明部分或者子圖層)吞鸭,創(chuàng)建出一個(gè)對(duì)應(yīng)形狀的陰影路徑就比較容易,而且Core Animation繪制這個(gè)陰影也相當(dāng)簡(jiǎn)單契耿,避免了屏幕外的圖層部分的預(yù)排版需求瞒大。這對(duì)性能來說很有幫助。

如果你的圖層是一個(gè)更復(fù)雜的圖形搪桂,生成正確的陰影路徑可能就比較難了,這樣子的話你可以考慮用繪圖軟件預(yù)先生成一個(gè)陰影背景圖盯滚。

混合和過度繪制

在第12章有提到踢械,GPU每一幀可以繪制的像素有一個(gè)最大限制(就是所謂的fill rate),這個(gè)情況下可以輕易地繪制整個(gè)屏幕的所有像素魄藕。但是如果由于重疊圖層的關(guān)系需要不停地重繪同一區(qū)域的話内列,掉幀就可能發(fā)生了。

GPU會(huì)放棄繪制那些完全被其他圖層遮擋的像素背率,但是要計(jì)算出一個(gè)圖層是否被遮擋也是相當(dāng)復(fù)雜并且會(huì)消耗處理器資源话瞧。同樣,合并不同圖層的透明重疊像素(即混合)消耗的資源也是相當(dāng)客觀的寝姿。所以為了加速處理進(jìn)程交排,不到必須時(shí)刻不要使用透明圖層。任何情況下饵筑,你應(yīng)該這樣做:

  • 給視圖的backgroundColor屬性設(shè)置一個(gè)固定的埃篓,不透明的顏色
  • 設(shè)置opaque屬性為YES

這樣做減少了混合行為(因?yàn)榫幾g器知道在圖層之后的東西都不會(huì)對(duì)最終的像素顏色產(chǎn)生影響)并且計(jì)算得到了加速,避免了過度繪制行為因?yàn)镃ore Animation可以舍棄所有被完全遮蓋住的圖層根资,而不用每個(gè)像素都去計(jì)算一遍架专。

如果用到了圖像,盡量避免透明除非非常必要玄帕。如果圖像要顯示在一個(gè)固定的背景顏色或是固定的背景圖之前部脚,你沒必要相對(duì)前景移動(dòng),你只需要預(yù)填充背景圖片就可以避免運(yùn)行時(shí)混色了裤纹。

如果是文本的話委刘,一個(gè)白色背景的UILabel(或者其他顏色)會(huì)比透明背景要更高效。

最后,明智地使用shouldRasterize屬性钱雷,可以將一個(gè)固定的圖層體系折疊成單張圖片骂铁,這樣就不需要每一幀重新合成了,也就不會(huì)有因?yàn)樽訄D層之間的混合和過度繪制的性能問題了罩抗。

減少圖層數(shù)量

初始化圖層拉庵,處理圖層,打包通過IPC發(fā)給渲染引擎套蒂,轉(zhuǎn)化成OpenGL幾何圖形钞支,這些是一個(gè)圖層的大致資源開銷。事實(shí)上操刀,一次性能夠在屏幕上顯示的最大圖層數(shù)量也是有限的烁挟。

確切的限制數(shù)量取決于iOS設(shè)備,圖層類型骨坑,圖層內(nèi)容和屬性等撼嗓。但是總得說來可以容納上百或上千個(gè),下面我們將演示即使圖層本身并沒有做什么也會(huì)遇到的性能問題欢唾。

裁切

在對(duì)圖層做任何優(yōu)化之前且警,你需要確定你不是在創(chuàng)建一些不可見的圖層,圖層在以下幾種情況下回事不可見的:

  • 圖層在屏幕邊界之外礁遣,或是在父圖層邊界之外斑芜。
  • 完全在一個(gè)不透明圖層之后。
  • 完全透明

Core Animation非常擅長(zhǎng)處理對(duì)視覺效果無意義的圖層祟霍。但是經(jīng)常性地杏头,你自己的代碼會(huì)比Core Animation更早地想知道一個(gè)圖層是否是有用的。理想狀況下沸呐,在圖層對(duì)象在創(chuàng)建之前就想知道醇王,以避免創(chuàng)建和配置不必要圖層的額外工作。

舉個(gè)例子垂谢。清單15.3 的代碼展示了一個(gè)簡(jiǎn)單的滾動(dòng)3D圖層矩陣厦画。這看上去很酷,尤其是圖層在移動(dòng)的時(shí)候(見圖15.1)滥朱,但是繪制他們并不是很麻煩根暑,因?yàn)檫@些圖層就是一些簡(jiǎn)單的矩形色塊。

清單15.3 繪制3D圖層矩陣

#import "ViewController.h"
#import <QuartzCore/QuartzCore.h>

#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
圖15.1 滾動(dòng)的3D圖層矩陣

WIDTH徙邻,HEIGHTDEPTH常量控制著圖層的生成排嫌。在這個(gè)情況下,我們得到的是10*10*10個(gè)圖層缰犁,總量為1000個(gè)淳地,不過一次性顯示在屏幕上的大約就幾百個(gè)怖糊。

如果把WIDTHHEIGHT常量增加到100,我們的程序就會(huì)慢得像龜爬了颇象。這樣我們有了100000個(gè)圖層伍伤,性能下降一點(diǎn)兒也不奇怪。

但是顯示在屏幕上的圖層數(shù)量并沒有增加遣钳,那么根本沒有額外的東西需要繪制扰魂。程序慢下來的原因其實(shí)是因?yàn)樵诠芾磉@些圖層上花掉了不少功夫。他們大部分對(duì)渲染的最終結(jié)果沒有貢獻(xiàn)蕴茴,但是在丟棄這么圖層之前劝评,Core Animation要強(qiáng)制計(jì)算每個(gè)圖層的位置,就這樣倦淀,我們的幀率就慢了下來蒋畜。

我們的圖層是被安排在一個(gè)均勻的柵格中,我們可以計(jì)算出哪些圖層會(huì)被最終顯示在屏幕上撞叽,根本不需要對(duì)每個(gè)圖層的位置進(jìn)行計(jì)算姻成。這個(gè)計(jì)算并不簡(jiǎn)單,因?yàn)槲覀冞€要考慮到透視的問題能扒。如果我們直接這樣做了佣渴,Core Animation就不用費(fèi)神了。

既然這樣初斑,讓我們來重構(gòu)我們的代碼吧。改造后膨处,隨著視圖的滾動(dòng)動(dòng)態(tài)地實(shí)例化圖層而不是事先都分配好见秤。這樣,在創(chuàng)造他們之前真椿,我們就可以計(jì)算出是否需要他鹃答。接著,我們?cè)黾右恍┐a去計(jì)算可視區(qū)域這樣就可以排除區(qū)域之外的圖層了突硝。清單15.4是改造后的結(jié)果测摔。

清單15.4 排除可視區(qū)域之外的圖層

#import "ViewController.h"
#import <QuartzCore/QuartzCore.h>

#define WIDTH 100
#define HEIGHT 100
#define DEPTH 10
#define SIZE 100
#define SPACING 150
#define CAMERA_DISTANCE 500
#define PERSPECTIVE(z) (float)CAMERA_DISTANCE/(z + CAMERA_DISTANCE)

@interface ViewController () <UIScrollViewDelegate>

@property (nonatomic, weak) 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;
}

- (void)viewDidLayoutSubviews
{
    [self updateLayers];
}

- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
    [self updateLayers];
}

- (void)updateLayers
{
    //calculate clipping bounds
    CGRect bounds = self.scrollView.bounds;
    bounds.origin = self.scrollView.contentOffset;
    bounds = CGRectInset(bounds, -SIZE/2, -SIZE/2);
    //create layers
    NSMutableArray *visibleLayers = [NSMutableArray array];
    for (int z = DEPTH - 1; z >= 0; z--)
    {
        //increase bounds size to compensate for perspective
        CGRect adjusted = bounds;
        adjusted.size.width /= PERSPECTIVE(z*SPACING);
        adjusted.size.height /= PERSPECTIVE(z*SPACING);
        adjusted.origin.x -= (adjusted.size.width - bounds.size.width) / 2;
        adjusted.origin.y -= (adjusted.size.height - bounds.size.height) / 2;
        for (int y = 0; y < HEIGHT; y++) {
        //check if vertically outside visible rect
            if (y*SPACING < adjusted.origin.y || y*SPACING >= adjusted.origin.y + adjusted.size.height)
            {
                continue;
            }
            for (int x = 0; x < WIDTH; x++) {
                //check if horizontally outside visible rect
                if (x*SPACING < adjusted.origin.x ||x*SPACING >= adjusted.origin.x + adjusted.size.width)
                {
                    continue;
                }
                
                //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
                [visibleLayers addObject:layer];
            }
        }
    }
    //update layers
    self.scrollView.layer.sublayers = visibleLayers;
    //log
    NSLog(@"displayed: %i/%i", [visibleLayers count], DEPTH*HEIGHT*WIDTH);
}
@end

這個(gè)計(jì)算機(jī)制并不具有普適性,但是原則上是一樣解恰。(當(dāng)你用一個(gè)UITableView或者UICollectionView時(shí)锋八,系統(tǒng)做了類似的事情)。這樣做的結(jié)果护盈?我們的程序可以處理成百上千個(gè)『虛擬』圖層而且完全沒有性能問題挟纱!因?yàn)樗恍枰淮涡詫?shí)例化幾百個(gè)圖層。

對(duì)象回收

處理巨大數(shù)量的相似視圖或圖層時(shí)還有一個(gè)技巧就是回收他們腐宋。對(duì)象回收在iOS頗為常見紊服;UITableViewUICollectionView都有用到檀轨,MKMapView中的動(dòng)畫pin碼也有用到,還有其他很多例子欺嗤。

對(duì)象回收的基礎(chǔ)原則就是你需要?jiǎng)?chuàng)建一個(gè)相似對(duì)象池参萄。當(dāng)一個(gè)對(duì)象的指定實(shí)例(本例子中指的是圖層)結(jié)束了使命,你把它添加到對(duì)象池中煎饼。每次當(dāng)你需要一個(gè)實(shí)例時(shí)讹挎,你就從池中取出一個(gè)。當(dāng)且僅當(dāng)池中為空時(shí)再創(chuàng)建一個(gè)新的腺占。

這樣做的好處在于避免了不斷創(chuàng)建和釋放對(duì)象(相當(dāng)消耗資源淤袜,因?yàn)樯婕暗絻?nèi)存的分配和銷毀)而且也不必給相似實(shí)例重復(fù)賦值。

好了衰伯,讓我們?cè)俅胃麓a吧(見清單15.5)

清單15.5 通過回收減少不必要的分配

@interface ViewController () <UIScrollViewDelegate>

@property (nonatomic, weak) IBOutlet UIScrollView *scrollView;
@property (nonatomic, strong) NSMutableSet *recyclePool;


@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad]; //create recycle pool
    self.recyclePool = [NSMutableSet set];
    //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;
}

- (void)viewDidLayoutSubviews
{
    [self updateLayers];
}

- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
    [self updateLayers];
}

- (void)updateLayers {
    
    //calculate clipping bounds
    CGRect bounds = self.scrollView.bounds;
    bounds.origin = self.scrollView.contentOffset;
    bounds = CGRectInset(bounds, -SIZE/2, -SIZE/2);
    //add existing layers to pool
    [self.recyclePool addObjectsFromArray:self.scrollView.layer.sublayers];
    //disable animation
    [CATransaction begin];
    [CATransaction setDisableActions:YES];
    //create layers
    NSInteger recycled = 0;
    NSMutableArray *visibleLayers = [NSMutableArray array];
    for (int z = DEPTH - 1; z >= 0; z--)
    {
        //increase bounds size to compensate for perspective
        CGRect adjusted = bounds;
        adjusted.size.width /= PERSPECTIVE(z*SPACING);
        adjusted.size.height /= PERSPECTIVE(z*SPACING);
        adjusted.origin.x -= (adjusted.size.width - bounds.size.width) / 2; adjusted.origin.y -= (adjusted.size.height - bounds.size.height) / 2;
        for (int y = 0; y < HEIGHT; y++) {
            //check if vertically outside visible rect
            if (y*SPACING < adjusted.origin.y ||
                y*SPACING >= adjusted.origin.y + adjusted.size.height)
            {
                continue;
            }
            for (int x = 0; x < WIDTH; x++) {
                //check if horizontally outside visible rect
                if (x*SPACING < adjusted.origin.x ||
                    x*SPACING >= adjusted.origin.x + adjusted.size.width)
                {
                    continue;
                }
                //recycle layer if available
                CALayer *layer = [self.recyclePool anyObject]; if (layer)
                {
                    
                    recycled ++;
                    [self.recyclePool removeObject:layer]; }
                else
                {
                    layer = [CALayer layer];
                    layer.frame = CGRectMake(0, 0, SIZE, SIZE); }
                //set position
                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
                [visibleLayers addObject:layer]; }
        } }
    [CATransaction commit]; //update layers
    self.scrollView.layer.sublayers = visibleLayers;
    //log
    NSLog(@"displayed: %i/%i recycled: %i",
          [visibleLayers count], DEPTH*HEIGHT*WIDTH, recycled);
}
@end

本例中铡羡,我們只有圖層對(duì)象這一種類型,但是UIKit有時(shí)候用一個(gè)標(biāo)識(shí)符字符串來區(qū)分存儲(chǔ)在不同對(duì)象池中的不同的可回收對(duì)象類型意鲸。

你可能注意到當(dāng)設(shè)置圖層屬性時(shí)我們用了一個(gè)CATransaction來抑制動(dòng)畫效果烦周。在之前并不需要這樣做,因?yàn)樵陲@示之前我們給所有圖層設(shè)置一次屬性怎顾。但是既然圖層正在被回收读慎,禁止隱式動(dòng)畫就有必要了,不然當(dāng)屬性值改變時(shí)槐雾,圖層的隱式動(dòng)畫就會(huì)被觸發(fā)夭委。

Core Graphics繪制

當(dāng)排除掉對(duì)屏幕顯示沒有任何貢獻(xiàn)的圖層或者視圖之后,長(zhǎng)遠(yuǎn)看來募强,你可能仍然需要減少圖層的數(shù)量株灸。例如,如果你正在使用多個(gè)UILabel或者UIImageView實(shí)例去顯示固定內(nèi)容擎值,你可以把他們?nèi)刻鎿Q成一個(gè)單獨(dú)的視圖慌烧,然后用-drawRect:方法繪制出那些復(fù)雜的視圖層級(jí)。

這個(gè)提議看上去并不合理因?yàn)榇蠹叶贾儡浖L制行為要比GPU合成要慢而且還需要更多的內(nèi)存空間鸠儿,但是在因?yàn)閳D層數(shù)量而使得性能受限的情況下屹蚊,軟件繪制很可能提高性能呢,因?yàn)樗苊饬藞D層分配和操作問題进每。

你可以自己實(shí)驗(yàn)一下這個(gè)情況汹粤,它包含了性能和柵格化的權(quán)衡,但是意味著你可以從圖層樹上去掉子圖層(用shouldRasterize品追,與完全遮擋圖層相反)玄括。

-renderInContext: 方法

用Core Graphics去繪制一個(gè)靜態(tài)布局有時(shí)候會(huì)比用層級(jí)的UIView實(shí)例來得快,但是使用UIView實(shí)例要簡(jiǎn)單得多而且比用手寫代碼寫出相同效果要可靠得多肉瓦,更邊說Interface Builder來得直接明了遭京。為了性能而舍棄這些便利實(shí)在是不應(yīng)該胃惜。

幸好,你不必這樣哪雕,如果大量的視圖或者圖層真的關(guān)聯(lián)到了屏幕上將會(huì)是一個(gè)大問題船殉。沒有與圖層樹相關(guān)聯(lián)的圖層不會(huì)被送到渲染引擎,也沒有性能問題(在他們被創(chuàng)建和配置之后)斯嚎。

使用CALayer-renderInContext:方法利虫,你可以將圖層及其子圖層快照進(jìn)一個(gè)Core Graphics上下文然后得到一個(gè)圖片,它可以直接顯示在UIImageView中堡僻,或者作為另一個(gè)圖層的contents糠惫。不同于shouldRasterize —— 要求圖層與圖層樹相關(guān)聯(lián) —— ,這個(gè)方法沒有持續(xù)的性能消耗钉疫。

當(dāng)圖層內(nèi)容改變時(shí)硼讽,刷新這張圖片的機(jī)會(huì)取決于你(不同于shouldRasterize,它自動(dòng)地處理緩存和緩存驗(yàn)證)牲阁,但是一旦圖片被生成固阁,相比于讓Core Animation處理一個(gè)復(fù)雜的圖層樹,你節(jié)省了相當(dāng)客觀的性能城菊。

總結(jié)

本章學(xué)習(xí)了使用Core Animation圖層可能遇到的性能瓶頸备燃,并討論了如何避免或減小壓力。你學(xué)習(xí)了如何管理包含上千虛擬圖層的場(chǎng)景(事實(shí)上只創(chuàng)建了幾百個(gè))凌唬。同時(shí)也學(xué)習(xí)了一些有用的技巧并齐,選擇性地選取光柵化或者繪制圖層內(nèi)容在合適的時(shí)候重新分配給CPU和GPU。這些就是我們要講的關(guān)于Core Animation的全部了客税。

文章摘錄自:https://github.com/AttackOnDobby/iOS-Core-Animation-Advanced-Techniques

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末冀膝,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子霎挟,更是在濱河造成了極大的恐慌,老刑警劉巖麻掸,帶你破解...
    沈念sama閱讀 221,430評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件酥夭,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡脊奋,警方通過查閱死者的電腦和手機(jī)熬北,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,406評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來诚隙,“玉大人讶隐,你說我怎么就攤上這事【糜郑” “怎么了巫延?”我有些...
    開封第一講書人閱讀 167,834評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵效五,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我炉峰,道長(zhǎng)畏妖,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,543評(píng)論 1 296
  • 正文 為了忘掉前任疼阔,我火速辦了婚禮戒劫,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘婆廊。我一直安慰自己迅细,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,547評(píng)論 6 397
  • 文/花漫 我一把揭開白布淘邻。 她就那樣靜靜地躺著茵典,像睡著了一般。 火紅的嫁衣襯著肌膚如雪列荔。 梳的紋絲不亂的頭發(fā)上敬尺,一...
    開封第一講書人閱讀 52,196評(píng)論 1 308
  • 那天,我揣著相機(jī)與錄音贴浙,去河邊找鬼砂吞。 笑死,一個(gè)胖子當(dāng)著我的面吹牛崎溃,可吹牛的內(nèi)容都是我干的蜻直。 我是一名探鬼主播,決...
    沈念sama閱讀 40,776評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼袁串,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼概而!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起囱修,我...
    開封第一講書人閱讀 39,671評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤赎瑰,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后破镰,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體餐曼,經(jīng)...
    沈念sama閱讀 46,221評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,303評(píng)論 3 340
  • 正文 我和宋清朗相戀三年鲜漩,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了源譬。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,444評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡孕似,死狀恐怖踩娘,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情喉祭,我是刑警寧澤养渴,帶...
    沈念sama閱讀 36,134評(píng)論 5 350
  • 正文 年R本政府宣布雷绢,位于F島的核電站,受9級(jí)特大地震影響厚脉,放射性物質(zhì)發(fā)生泄漏习寸。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,810評(píng)論 3 333
  • 文/蒙蒙 一傻工、第九天 我趴在偏房一處隱蔽的房頂上張望霞溪。 院中可真熱鬧,春花似錦中捆、人聲如沸鸯匹。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,285評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽殴蓬。三九已至,卻和暖如春蟋滴,著一層夾襖步出監(jiān)牢的瞬間染厅,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,399評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工津函, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留肖粮,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,837評(píng)論 3 376
  • 正文 我出身青樓尔苦,卻偏偏與公主長(zhǎng)得像涩馆,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子允坚,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,455評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容