2018版Instruments學(xué)習(xí)之Core Animation、Time Profiler

Instruments是Xcode組件中沒有被充分利用的一個(gè)工具。很多iOS開發(fā)者從沒用過Instruments,或者只是用Leaks工具檢測循環(huán)引用。實(shí)際上有很多Instruments工具,例如為動(dòng)畫性能調(diào)優(yōu)的東西。這里我將介紹其中的兩樣工具Core Animation,Time Profiler,用來優(yōu)化系統(tǒng)性能。

你可以通過在菜單中選擇Product --> Profile選項(xiàng)來打開Instruments(在這之前,記住要把目標(biāo)設(shè)置成真機(jī)iOS設(shè)備,而不是模擬器)。然后將會(huì)顯示下圖(如果沒有看到所有選項(xiàng),你可能設(shè)置成了模擬器選項(xiàng))。

PS:我用自己的手機(jī),在打開測試工具的時(shí)候,會(huì)一直停留在初始化階段,用公司的測試機(jī)就不會(huì)有問題。所以,如果你發(fā)現(xiàn)你使用不了Instruments工具的時(shí)候,就換個(gè)手機(jī)或者升級Xcode再試一下吧(手機(jī)版本與Xcode版本對應(yīng))。后面是stackoverflow里面一個(gè)相似的問題:https://stackoverflow.com/questions/49744280/time-profiler-in-instruments-is-not-working

你應(yīng)該始終將程序設(shè)置成發(fā)布選項(xiàng)。幸運(yùn)的是,配置文件默認(rèn)就是發(fā)布選項(xiàng),所以你不需要在分析的時(shí)候調(diào)整編譯策略。

Instruments的一個(gè)很棒的功能在于它可以創(chuàng)建我們自定義的工具集。除了你初始選擇的工具之外,如果在Instruments中點(diǎn)擊+號,你可以拖拽別的工具到左側(cè)邊欄。我們將創(chuàng)建以上我們提到的兩個(gè)工具,然后就可以并行使用了。

Time Profiler調(diào)試選項(xiàng)

時(shí)間分析器有一些選項(xiàng)來幫助我們定位到我們關(guān)心的的方法??梢渣c(diǎn)擊下面的Call Tree打開。其中最有用的是如下幾點(diǎn):

1.Separate By Thread(線程分離)

這可以通過執(zhí)行的線程進(jìn)行分組。如果代碼被多線程分離的話,那么就可以判斷到底是哪個(gè)線程造成了問題。

2.Hide System Library(隱藏系統(tǒng)庫)

可以隱藏所有蘋果的框架代碼,來幫助我們尋找哪一段代碼造成了性能瓶頸。由于我們不能優(yōu)化框架方法,所以這對定位到我們能實(shí)際修復(fù)的代碼很有用。

Core Animation的調(diào)試選項(xiàng)

Core Animation不僅僅可以用來查看視圖的FPS(Frame per second),我們還可以利用它的許多選項(xiàng)來分析系統(tǒng)性能。

1.Color Blended Layers (圖層混合)

Instruments可以在物理機(jī)上顯示出被混合的圖層Blended Layer(用紅色標(biāo)注),Blended Layer是因?yàn)檫@些Layer是透明的(Transparent),系統(tǒng)在渲染這些view時(shí)需要將該view和下層view混合(Blend)后才能計(jì)算出該像素點(diǎn)的實(shí)際顏色,如果這種blended layer很多,那么在滾動(dòng)列表時(shí)就會(huì)出現(xiàn)卡頓。

解決blended layer問題也很簡單,檢查紅色區(qū)域view的opaque屬性,記得設(shè)置成YES;將backgroundColor屬性設(shè)置成不是[UIColor clearColor]的對象。

會(huì)出現(xiàn)圖層混合的原因有以下幾項(xiàng):

  • 視圖控件的backgroundColor是透明的
  • 對于UIImageView,它的image是包含透明通道的png文件
  • opaque屬性為NO(不過默認(rèn)好像就是Yes)
  • alpha屬性小于1

2.ColorHitsGreenandMissesRed(柵格化)

很多視圖Layer由于Shadow、Mask和Gradient等原因渲染很高,因此UIKit提供了API用于緩存這些Layer:[layer setShouldRasterize:YES],系統(tǒng)會(huì)將這些Layer緩存成Bitmap位圖供渲染使用,如果失效時(shí)便丟棄這些Bitmap重新生成。圖層Rasterization柵格化好處是對刷新率影響較小,壞處是刪格化處理后的Bitmap緩存需要占用內(nèi)存,而且當(dāng)圖層需要縮放時(shí),要對刪格化后的Bitmap做額外計(jì)算。 使用這個(gè)選項(xiàng)后時(shí),如果Rasterized的Layer失效,便會(huì)標(biāo)注為紅色,如果有效標(biāo)注為綠色。當(dāng)測試的應(yīng)用頻繁閃現(xiàn)出紅色標(biāo)注圖層時(shí),表明對圖層做的Rasterization作用不大。

具體使用可以看下面的例子。

3.Color Copied Images

有時(shí)候寄宿圖片的生成意味著Core Animation被強(qiáng)制生成一些圖片,然后發(fā)送到渲染服務(wù)器,而不是簡單的指向原始指針。如應(yīng)用中有一些從網(wǎng)絡(luò)下載的圖片,而GPU恰好不支持這個(gè)格式,這就需要CPU預(yù)先進(jìn)行格式轉(zhuǎn)化,這個(gè)選項(xiàng)把這些圖片渲染成藍(lán)色。復(fù)制圖片對內(nèi)存和CPU使用來說都是一項(xiàng)非常昂貴的操作,所以應(yīng)該盡可能的避免。

4.Color Non-Standard Surface Formats

不標(biāo)準(zhǔn)的表面顏色格式。

5.Color Immediately

通常Core Animation Instruments以每毫秒10次的頻率更新圖層調(diào)試顏色。對某些效果來說,這顯然太慢了。這個(gè)選項(xiàng)就可以用來設(shè)置每幀都更新(可能會(huì)影響到渲染性能,而且會(huì)導(dǎo)致幀率測量不準(zhǔn),所以不要一直都設(shè)置它)。

6.Color Misaligned Images

這個(gè)選項(xiàng)檢查了圖片是否被縮放,以及像素是否對齊。被放縮的圖片會(huì)被標(biāo)記為黃色,像素不對齊則會(huì)標(biāo)注為紫色。黃色、紫色越多,性能越差。

7.Color Offscreen-Rendered Yellow(離屏渲染)

這個(gè)選項(xiàng)會(huì)把那些離屏渲染的圖層顯示為黃色。黃色越多,性能越差。這些顯示為黃色的圖層很可能需要用shadowPath或者shouldRasterize來優(yōu)化。

可能會(huì)出現(xiàn)離屏渲染的原因有以下幾項(xiàng):

  • 重寫drawRect方法(重繪)
  • 使用了CALayer的mask屬性
  • 添加了陰影效果(可以使用shadowpath屬性解決)
  • CALayer的shouldRasterize屬性為YES(開啟柵格化)
  • 對于好多文章寫的同時(shí)使用cornerRadiusmasksToBounds就會(huì)開啟離屏渲染,我測試的時(shí)候倒是沒有出現(xiàn)過,所以我只能建議你自己可以測試一下。

8.Color Compositing Fast Path Blue,

這個(gè)選項(xiàng)會(huì)把任何直接使用 OpenGL 繪制的圖層顯示為藍(lán)色。藍(lán)色越多,性能越好。如果僅僅使用 UIKit 或者 Core Animation 的 API,那么不會(huì)有任何效果。如果使用 GLKView 或者 CAEAGLLayer,那如果不顯示藍(lán)色塊的話就意味著你正在強(qiáng)制 CPU 渲染額外的紋理,而不是繪制到屏幕。

9.Flash Updated Regions

這個(gè)選項(xiàng)會(huì)把重繪的內(nèi)容顯示為黃色。出現(xiàn)的黃色越多,性能越差。通常我們希望只是更新的部分被標(biāo)記完黃色。

PS: 上面所說的一些選項(xiàng)也可以同樣在模擬器的Debug菜單工具中使用。雖然使用模擬器測試性能可能并不是很好(模擬機(jī)上測試性能可能會(huì)失真,例如一個(gè)動(dòng)畫在模擬器上運(yùn)行流暢,但在真機(jī)上會(huì)出現(xiàn)卡頓),但如果你能通過這些高亮選項(xiàng)識(shí)別出性能問題出現(xiàn)在什么地方的話,那么使用模擬器來驗(yàn)證問題也許會(huì)比真機(jī)測試更加有效。

創(chuàng)建一個(gè)測試用例

我們創(chuàng)建一個(gè)簡單的顯示模擬聯(lián)系人姓名和頭像列表的應(yīng)用。注意為了使應(yīng)用看起來更真實(shí),即使把頭像圖片存在應(yīng)用本地,我們也需要實(shí)時(shí)加載圖片,而不是用–imageNamed:預(yù)加載。同樣添加一些圖層陰影來使得列表顯示得更真實(shí)。下面的代表是最初版本的實(shí)現(xiàn):


#import "ViewController.h"

@interface ViewController () <UITableViewDelegate, UITableViewDataSource>
@property (nonatomic, strong) NSArray *items;
@property (nonatomic, strong) UITableView *tableView;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSMutableArray *array = @[].mutableCopy;
    for (NSInteger i = 0; i < 10; i++) {
        [array addObject:[NSString stringWithFormat:@"image%02ld",(i+1)%10 == 0 ? 10 : (i+1)%10]];
    }
    self.items = array;
    
    [self.view addSubview:self.tableView];
    // Do any additional setup after loading the view, typically from a nib.
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return self.items.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"UITableViewCell" forIndexPath:indexPath];
    //load image
    NSString *filePath = [[NSBundle mainBundle] pathForResource:self.items[indexPath.row] ofType:@"png"];
    //set image and text
    cell.imageView.image = [UIImage imageWithContentsOfFile:filePath];

    cell.textLabel.text = [NSString stringWithFormat:@"name %02ld",indexPath.row];
    //set image shadow
    cell.imageView.layer.shadowOffset = CGSizeMake(0, 5);
    cell.imageView.layer.shadowOpacity = 0.75;
    cell.clipsToBounds = YES;
    //set text shadow
    cell.textLabel.backgroundColor = [UIColor clearColor];
    cell.textLabel.layer.shadowOffset = CGSizeMake(0, 2);
    cell.textLabel.layer.shadowOpacity = 0.5;
    return cell;
}


- (UITableView *)tableView
{
    if (_tableView == nil) {
        _tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStylePlain];
        [_tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"UITableViewCell"];
        _tableView.delegate = self;
        _tableView.dataSource = self;
    }
    return _tableView;
}

@end

可以看到滑動(dòng)的時(shí)候會(huì)有卡頓,F(xiàn)PS不超過20。

僅憑直覺,我們猜測性能瓶頸應(yīng)該在圖片加載。我們實(shí)時(shí)從硬盤加載圖片,而且沒有緩存,所以很可能是這個(gè)原因。我們可以用一些代碼修復(fù),使用GCD異步加載圖片,然后將它們緩存起來...在開始編碼之前,最好先測試一下假設(shè)是否成立。首先用我們的兩個(gè)Instruments工具分析一下程序來定位問題。我們推測問題可能和圖片加載相關(guān),所以用Time Profiler工具來試試。

-tableView:cellForRowAtIndexPath:中的CPU利用率只有3.8%(也就是加載頭像圖片的地方),這還是比較低的。于是,CPU利用率并不是卡頓的真正因素。接下來我們看一下是不是GPU的問題

1.檢查圖層混合

我們來用Core Animation調(diào)試工具選項(xiàng)來檢查屏幕,首先打開Color Blended Layers。

屏幕中所有紅色的部分都意味著字符標(biāo)簽視圖的高級別混合,這很正常,因?yàn)槲覀儼驯尘霸O(shè)置成了透明色來顯示陰影效果。這就解釋了為什么渲染利用率這么高了。

只開啟Color Blended Layers,然后沒有混合的部分會(huì)是綠色,混合最嚴(yán)重的部分會(huì)是紅色。大量的圖層混合會(huì)消耗GPU的時(shí)間,因?yàn)閷τ谝粋€(gè)像素點(diǎn),GPU不能簡單的使用最上層的視圖的顏色,而是需要進(jìn)行計(jì)算疊加。

2.檢查離屏渲染

打開Core Animation工具的Color Offscreen - Rendered Yellow選項(xiàng)

如圖所示,所有的表格單元內(nèi)容都在離屏渲染。這是因?yàn)槲覀兘o圖片和文本視圖添加的陰影效果。在代碼中禁用陰影,看下性能是否會(huì)提高。

3.如何保持陰影效果并且不會(huì)影響性能

我們已經(jīng)找到了卡頓的原因,禁止陰影之后的滑動(dòng)很流暢。但是我們的列表看起來沒有之前的好了。那么如何保持陰影效果并且不會(huì)影響性能呢?

在這個(gè)demo中,我們并不需要頭像及文字動(dòng)態(tài)的改變,即當(dāng)列表創(chuàng)建完成之后,每一行的文字和頭像在每一幀刷新的時(shí)候都不需要改變。我們可以使用CALayer的shouldRasterize屬性里緩存圖層內(nèi)容,圖層會(huì)在離屏渲染一次之后將結(jié)果保存起來,在后面刷新的時(shí)候都可以將這個(gè)結(jié)果拿出來用。

下面是修改之后的主要代碼:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"UITableViewCell" forIndexPath:indexPath];
    //load image
    NSString *filePath = [[NSBundle mainBundle] pathForResource:self.items[indexPath.row] ofType:@"png"];
    //set image and text
    cell.imageView.image = [UIImage imageWithContentsOfFile:filePath];

    cell.textLabel.text = [NSString stringWithFormat:@"name %02ld",indexPath.row];
    //set image shadow
    cell.imageView.layer.shadowOffset = CGSizeMake(0, 5);
    cell.imageView.layer.shadowOpacity = 0.75;
    cell.clipsToBounds = YES;
    //set text shadow
    cell.textLabel.backgroundColor = [UIColor clearColor];
    cell.textLabel.layer.shadowOffset = CGSizeMake(0, 2);
    cell.textLabel.layer.shadowOpacity = 0.5;
    
    cell.layer.shouldRasterize = YES;
    cell.layer.rasterizationScale = [UIScreen mainScreen].scale;
    
    return cell;
}
圖片08.png

使用Core Animation工具的Color Hits Green and Misses Red選項(xiàng),屏幕都是綠色,只有當(dāng)滑動(dòng)到屏幕時(shí)會(huì)有一會(huì)變成紅色(綠色代表使用了緩存,紅色代表緩存再生)?;瑒?dòng)時(shí)FPS保持在50以上,比較平順。

結(jié)尾

所以我們初始的設(shè)想是錯(cuò)的。圖片的加載并不是真正的瓶頸所在,而且試圖把它置于一個(gè)復(fù)雜的多線程加載和緩存的實(shí)現(xiàn)都將是徒勞。所以在動(dòng)手修復(fù)之前找出問題所在是個(gè)很好的習(xí)慣!

上面就是我使用Instruments中Core Animation,Time Profiler兩個(gè)工具來分析系統(tǒng)性能的一點(diǎn)研究,這次的示例代碼地址:Demo,如果對你有幫助的話請點(diǎn)個(gè)??哈!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容