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í)使用
cornerRadius和masksToBounds就會(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;
}

使用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è)??哈!