iOS性能調優(yōu)實戰(zhàn)之Instrument-Core Animation

由于app新版本增加好多動畫,本身就是一個拍照為主的建模工具。所以對性能要求很高,所以性能調優(yōu)尤為重要。Instrument有很多工具可以測試內存泄漏,測試動畫幀率,所以用好它,能解決項目中卡頓問題,讓app更健壯。(隨便扯點先)

  • 先看下基本界面,點擊Product->Profile 或者直接快捷鍵Command + i 打開Instrument
BEBFE50F-80E7-4FE2-9A19-A41492C029C2.png

Core Animation

它的作用就是查看fps的值,稍后會說這個fps是什么意思。第二個作用就是查看圖層的渲染,圖層的渲染是影響fps的關鍵。如果你的tableview滑動有卡頓,首先你會看到fps比較小。然后就查找原因,原因就在圖層渲染上。如果渲染問題解決了,那fps就接近最高了,最高的是60。

FPS 屏幕每秒刷新的頻率

任何屏幕總有一個刷新率,比如iphone推薦的刷新率是60Hz,也就是說GPU每秒鐘刷新屏幕60次,因此兩次刷新之間的間隔為16.67ms。這段時間內屏幕內容保持不變,稱為一幀(frame),fps表示frames per second,也就是每秒鐘顯示多少幀畫面。對于靜止不變的內容,我們不需要考慮它的刷新率,但在執(zhí)行動畫或滑動時,fps的值直接反映出滑動的流暢程度。

注意用真機測試哦,點擊上圖的Core Animation,然后進入下圖的界面,然后點左上角的??按鈕,就可以開始測試了。這就是我滑動tabelview的時候屏幕刷新的頻次。0的時候就是我沒有滑動。滑動的時候大約都是在50多(60最佳),說明滑動的還算順。根據WWDC的說法,當FPS 低于45的時候,用戶就會察覺到到滑動有卡頓。那就該分析下卡頓的原因。


7492DEB4-2898-4589-8C51-D0D6B246A32D.png
調試、優(yōu)化,查看圖層的渲染情況(關鍵問題)
屏幕快照 2017-07-08 下午12.18.15.png

我們可以用上圖底部的Debug Options框子里的工具去調試。尋找掉幀的原因。接下來就一個個去用用它們。

Color Blended Layers 圖層混合

三原色大家都知道吧,rgb,如果某一塊區(qū)域上覆蓋了多個view,最后的顯示效果受到這些view的共同影響。舉個例子,上層是藍色(RGB=0,0,1),透明度為50%,下層是紅色(RGB=1,0,0)。那么最終的顯示效果是紫色(RGB=0.5,0,0.5)。這種顏色的混合(blending)需要消耗一定的GPU資源,因為實際上可能不止只有兩層。如果只想顯示最上層的藍色,可以把它的透明度設置為100%,這樣GPU會忽略下面所有的layer,從而節(jié)約了很多不必要的運算。

當勾選了Color Blended Layers之后會看到手機出現(xiàn)這樣的場景,紅色代表出先了圖層混合,綠色的沒事,所以我們就努力的去消除紅色就行了。

IMG_6346.PNG

首先創(chuàng)建一個View,應該給他個顏色,不給就是默認透明,透明就有可能發(fā)生圖層混合,所以這個是一個點。

UIImageView可以先給背景一個顏色,不僅它自身需要是不透明的,它的圖片也不能含有alpha通道,如果有alpha通道就會發(fā)生圖層混合。

label(哈哈哈)成紅色,是因為一沒有給文字的label增加不透明的背景顏色,而是當UILabel內容為中文時,label的實際渲染區(qū)域要大于label的size,因為外圍有了一圈的陰影,才會出現(xiàn)圖層混合所以就把超出的部分切一下,代碼如下:

   UILabel *textLabel = [[UILabel alloc]initWithFrame:CGRectMake(100, 20, 80, 20)];
    textLabel.backgroundColor = [UIColor whiteColor];
    textLabel.layer.masksToBounds = YES;
    textLabel.text = [NSString stringWithFormat:@"哈哈哈"];
    [self.contentView addSubview:textLabel];

最后發(fā)現(xiàn)哈哈哈變綠了。

Color Hits Green and Misses Red 光柵化緩存圖層命中

光柵化是將一個layer預先渲染成位圖(bitmap),然后加入緩存中。如果對于陰影效果這樣比較消耗資源的靜態(tài)內容進行緩存,可以得到一定幅度的性能提升。

label.layer.shouldRasterize = true
label.layer.rasterizationScale = layer.contentsScale 

它表示如果命中緩存則顯示為綠色,否則顯示為紅色,顯然綠色越多越好,紅色越少越好。

光柵化的核心在于緩存的思想。上下微小幅度滑動時,一直是綠色
上下較大幅度滑動,新出現(xiàn)的label一開始是紅色,隨后變成綠色
如果靜止一秒鐘,剛開始滑動時會變紅。
這是因為layer進行光柵化后渲染成位圖放在緩存中。當屏幕出現(xiàn)滑動時,我們直接從緩存中讀取而不必渲染,所以會看到綠色。當新的label出現(xiàn)時,緩存中沒有個這個label的位圖,所以會變成紅色。第三點比較關鍵,緩存中的對象有效期只有100ms,即如果在0.1s內沒有被使用就會自動從緩存中清理出去。這就是為什么停留一會兒再滑動就會看到紅色。

光柵化的緩存機制是一把雙刃劍,先寫入緩存再讀取有可能消耗較多的時間。因此光柵化僅適用于較復雜的、靜態(tài)的效果。通過Instrument的調試發(fā)現(xiàn),如果使用光柵化經常出現(xiàn)未命中緩存的情況,如果沒有特殊需要則可以關閉光柵化,而且光柵化會導致離屏渲染。

Color Copied Image (拷貝的圖片)

這個選項主要檢查我們有無使用不正確圖片格式,由于手機顯示都是基于像素的,所以當手機要顯示一張圖片的時候,系統(tǒng)會幫我們對圖片進行轉化。比如一個像素占用一個字節(jié),故而RGBA則占用了4個字節(jié),則1920 x 1080的圖片占用了7.9M左右,但是平時jpg或者png的圖片并沒有那么大,因為它們對圖片做了壓縮,但是是可逆的。所以此時,如果圖片的格式不正確,則系統(tǒng)將圖片轉化為像素的時間就有可能變長。而該選項就是檢測圖片的格式是否是系統(tǒng)所支持的,若是GPU不支持的色彩格式的圖片則會標記為青色,則只能由CPU來進行處理。CPU被強制生成了一些圖片,然后發(fā)送到渲染服務器,而不是簡單的指向原始圖片的的指針。我們不希望在滾動視圖的時候,CPU實時來進行處理,因為有可能會阻塞主線程。一般不會出現(xiàn)這種情況。如果有則會將圖片標記為藍色。

Color Misaligned Image (圖片大小)

這里會高亮那些被縮放或者拉伸以及沒有正確對齊到像素邊界的圖片,即圖片Size和imageView中的Size不匹配,會使圖過程片縮放,而縮放會占用CPU,所以在寫代碼的時候保證圖片的大小匹配好imageView,如果圖片需要縮放則標記為黃色,如果沒有像素對齊則標記為紫色。

Color Offscreen- Rendered Yellow (離屏渲染)

離屏渲染就是GPU渲染發(fā)生在屏幕外。

先看看正常的渲染首先OpenGL提交一個命令到Command Buffer,隨后GPU開始渲染,渲染結果放到Render Buffer中,這是正常的渲染流程。

但是有一些復雜的效果無法直接渲染出結果,它需要分步渲染最后再組合起來,比如為相機圖標添加一個蒙版(mask):在前兩個渲染通道中,GPU分別得到了紋理(texture,也就是那個相機圖標)和layer(藍色的蒙版)的渲染結果。但這兩個渲染結果沒有直接放入Render Buffer中,也就表示這是離屏渲染。直到第三個渲染通道,才把兩者組合起來放入Render Buffer中。離屏渲染意味著把渲染結果臨時保存,等用到時再取出,因此相對于普通渲染更占用資源。

Color Offscreen-Rendered Yellow”會把需要離屏渲染的地方標記為黃色,大部分情況下我們需要盡可能避免黃色的出現(xiàn)。離屏渲染可能會自動觸發(fā),也可以手動觸發(fā)。以下情況可能會導致觸發(fā)離屏渲染:

  • 為圖層設置遮罩(layer.mask)
  • 將圖層的layer.masksToBounds / view.clipsToBounds屬性設置為true
  • 將圖層layer.allowsGroupOpacity屬性設置為YES和layer.opacity小于1.0
  • 為圖層設置陰影(layer.shadow *)。
  • 為圖層設置layer.shouldRasterize=true
  • 具有l(wèi)ayer.cornerRadius,layer.edgeAntialiasingMask,layer.allowsEdgeAntialiasing的圖層
  • 文本(任何種類,包括UILabel,CATextLayer,Core Text等)。
  • 使用CGContext在drawRect :方法中繪制大部分情況下會導致離屏渲染,甚至僅僅是一個空的實現(xiàn)。

如果設置陰影,可以給陰影一個路徑,

imgView.layer.shadowPath = UIBezierPath(rect: imgView.bounds).CGPath

這行代碼制定了陰影路徑,如果沒有手動指定,Core Animation會去自動計算,這就會觸發(fā)離屏渲染。如果人為指定了陰影路徑,就可以免去計算,從而避免產生離屏渲染。

設置cornerRadius本身并不會導致離屏渲染,但很多時候它還需要配合layer.masksToBounds = true使用。根據之前的總結,設置masksToBounds會導致離屏渲染。解決方案是盡可能在滑動時避免設置圓角,如果必須設置圓角,可以使用光柵化技術將圓角緩存起來或者是繪制一個圓角圖片:

// 設置圓角
label.layer.masksToBounds = true
label.layer.cornerRadius = 8
label.layer.shouldRasterize = true
label.layer.rasterizationScale = layer.contentsScale

總結

避免圖層混合

確保控件的opaque屬性設置為true,確保backgroundColor和父視圖顏色一致且不透明
如無特殊需要,不要設置低于1的alpha值
確保UIImage沒有alpha通道

避免臨時轉換

確保圖片大小和frame一致,不要在滑動時縮放圖片
確保圖片顏色格式被GPU支持,避免勞煩CPU轉換

慎用離屏渲染

絕大多數(shù)時候離屏渲染會影響性能
重寫drawRect方法,設置圓角、陰影、模糊效果,光柵化都會導致離屏渲染
設置陰影效果是加上陰影路徑
滑動時若需要圓角效果,開啟光柵化

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容