更新:2019年7月10号
卡頓分析方案:
1、根據主線程Runloop的狀态(kCFRunLoopBeforeSources和kCFRunLoopAfterWaiting,即正在處理任務的狀态),做實時監控主線程卡頓。(可以參考:iOS實時卡頓監控)
2、監控FPS,要保持流暢的UI互動,App 重新整理率應該努力保持在 60fps。(可以參考:微信讀書團隊GYMonitor工具)
更新:2018年4月20号
關于離屏渲染的詳細介紹:iOS"離屏渲染"整理總結
離屏渲染優化詳解:離屏渲染優化詳解:執行個體示範+性能測試
圓角性能問題解決辦法:設定圓角引發性能問題的解決辦法
前言
工欲善其事,必先利其器。Instrument對于iOS開發來說,是發現并且解決問題的一把利器。
本文會用到的兩個工具包括:
- Time Profiler(擷取代碼運作時間,一般用來看CPU占用)
- Core Animation(擷取圖形繪制情況,FPS,離屏渲染等)
界面顯示的原理
iOS裝置通常是60fps(每秒60幀),也就是說兩幀相隔的時間是1/60秒,大概16.7ms。在這16.7ms中,為了顯示一幀,需要如下工作
- CPU計算好各個視圖的位置,大小,對圖檔進行解碼等,繪制成紋理交給GPU
- GPU對收到的紋理進行混合,頂點變換,渲染到幀緩沖區
- 每16.7ms,一個時鐘信号到達,幀緩沖區取出一幀,顯示到螢幕。
也就是說,CPU或者GPU被大量占用的時候,都有可能在16.7ms中沒辦法完成一幀的繪制,導緻時鐘信号到來的時候,取得還是上一幀的内容,也就都有可能導緻界面卡頓
離屏渲染
在iOS中,渲染通常分為CPU和GPU渲染兩種,而GPU渲染又分為在GPU緩沖區和非GPU緩沖區兩種
- CPU渲染(軟體渲染),CPU繪制成bitmap,交給GPU
- GPU渲染(硬體渲染)
- GPU緩沖區渲染
- 非GPU緩沖區渲染(額外開辟緩沖區)
通常,CPU渲染,和GPU非幀緩沖區内渲染統稱為離屏渲染。因為,CPU和幀緩沖區是為圖形圖像顯示做了高度優化的,速度較快。
什麼情況下會觸發離螢幕渲染?
- 用CoreGraphics的CGContext繪制的
- 在
中繪制的,即使drawRect
是空的drawRect
- Layer具有
(比如圓角)或者Mask
Shadow
- Layer的隔栅化
shouldRasterize為True
- 文本(UILabel,UITextfield,UITextView,CoreText,UITextLayer等)
離屏渲染一定會引起性能問題嗎?
很少會,比如
drawRect
這個方法,隻會在時圖進行重新繪制的時候才會調用。也就是說,假如你的View并不會頻繁重繪,那麼即使實作了
drawRect
,也沒什麼關系。
對了,目前iOS裝置的硬體越來越好也是一個原因,想要要性能差也挺難的。
CoreGraphics VS CALayer
上文提到了,CoreGraphics通常是CPU渲染成bitmap交給GPU,假如頻繁的大量的繪制出現,往往會導緻界面卡頓。而CALayer是對GPU做過優化的,能夠硬體加速。是以,對于性能要求較高的繪制,嘗試用CALayer替代CoreGraphics
一個反面教材
一定要在真機上測試性能才有意義,本文是采用iPhone 5s來調試的。一般測試性能支援的性能最差的就可以了,如果是iOS 8要測試4s上的性能。
界面很簡單,一個ImageView,右側是随機生成的100個字元,富文本顯示。
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsISO2IjM1UDN1EjNygDM2EDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
Time Profile
1.打開Time Profile,然後運作想要分析的App
2.進入主界面,上下滾動List,讓Time Profile采集資料,
勾選右側的
- Separate by Thread,按線程區分
- Invert Call Tree ,逆向Call Tree,友善我們檢視方法調用順序
- Hide System Libraries,隐藏系統的庫,因為通常系統的代碼并不會影響性能
3.可以選擇一段時間,來分析這段時間CPU的使用情況
4.找到占用時間最多的代碼
然後,輕按兩下占用最多的這一行,進入實際的代碼,看看到底哪裡占用比較多
這裡,我們看到是這一行代碼
cell.testLabel?.attributedText = mutableAttr
。
占用最多的CPU時間。
我們先來看下整個方法代碼,
-
其實很簡單,就一個TableViewCell
(帶圓角,陰影),一個ImageView
UILabel
-
裡會随機的生成100個字元,然後用cellForRowAtIndexPath
來讓AttributeText
顯示UILabel
乍一看,問題應該是這個随機生成100個字元的函數啊
func calculateRandomText()->String{
var result = ""
for _ in 0...100{
let random = getRandomCharFromString(globalStr)
result.appendContentsOf(random)
}
return result
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
因為,每一次CellForRow調用的時候,都會計算100次。然後,我們實際分析的時候,發現其實100次對顯示來說,真不算什麼,也不是卡頓的原因。
那麼,為什麼設定
attributeText
占用時間這麼多呢?
其實很簡單,
attributeText
是建立在TextKit上的,由于每一次顯示都是随機的
attributeText
,每一次都要重新計算文本的大小,位置等等。另外,UIKit中,提供的文本渲染都是在CPU中進行的,渲染成Bitmap,然後交給GPU,是以導緻設定attributeText的時候,占用很多時間。
這裡不得不提到:一定不要過早優化,優化的時候盡量依賴于Instrument的分析結果,而不是自己的主觀感受。尤其當你還是個新司機的時候。
Core Animation
在Instrument中,Command+L打開Library,然後添加Core Animation。我們來看看GPU的相關的問題
最直覺的就是滾動視圖,檢視FPS(Frame per second),一般小于50幀就會看到明顯的掉幀。
備注:這裡的很多參考自這本書
1.看看圖層混合情況
- 隻開啟
,然後沒有混合的部分會是綠色,混合最嚴重的部分會是紅色。大量的圖層混合會消耗GPU的時間,因為對于一個像素點,GPU不能簡單的使用最上層的視圖的顔色,而是需要進行計算疊加。Color Blended Layers
會看到截圖如下
這裡的Cell整個背景都是紅的,因為背景是
alpha
為0.3的View,
UILabel
是深紅色的,因為大量的陰影。
2.看看隔栅化情況,
- 隻開啟
,當使用shouldRasterize屬性的時候,耗時的圖層繪制會被緩存,然後當做一個簡單的扁平圖檔呈現。當緩存無法使用必須重建的時候,會被高亮為紅色。Color Hits Green and Misses Red
截圖如下:
3.看看拷貝圖檔情況
- 隻開啟
- 有時候寄宿圖檔的生成意味着Core Animation被強制生成一些圖檔,然後發送到渲染伺服器,而不是簡單的指向原始指針。這個選項把這些圖檔渲染成藍色。複制圖檔對記憶體和CPU使用來說都是一項非常昂貴的操作,是以應該盡可能的避免。Color Copied Images
我的測試項目裡沒有這個,是以不貼圖了。
4.看看圖檔有沒有像素不對齊,有沒有拉伸和縮放
-
,可以看到如下。(因為我們的縮略圖其實是一張很大的圖,是以被縮放了,導緻顯示成黃色)Color Misaligned Images
5.看看離屏渲染
-隻開啟
Color Offscreen-Rendered Yellow
,離螢幕渲染的部分會被高亮成黃色
6.其他選項
- Color Immediately 通常Core Animation Instruments以每毫秒10次的頻率更新圖層調試顔色。對某些效果來說,這顯然太慢了。這個選項就可以用來設定每幀都更新
- Color OpenGL Fast Path Blue 這個選項會對任何直接使用OpenGL繪制的圖層進行高亮
- Flash Updated Region 這個選項會對重繪的内容高亮成黃色(也就是任何在軟體層面使用Core Graphics繪制的圖層)。這種繪圖的速度很慢。
界面頓卡的原因
界面頓卡主要從兩個角度考慮
CPU限制
- 對象的建立,釋放,屬性調整。這裡尤其要提一下屬性調整,CALayer的屬性調整的時候是會建立隐式動畫的,是比較損耗性能的。
- 視圖和文本的布局計算,AutoLayout的布局計算都是在主線程上的,是以占用CPU時間也很多 。U
- 文本渲染,諸如UILabel和UITextview都是在主線程渲染的
- 圖檔的解碼,這裡要提到的是,通常UIImage隻有在交給GPU之前的一瞬間,CPU才會對其解碼。
GPU限制
- 視圖的混合。比如一個界面十幾層的視圖疊加到一起,GPU不得不計算每個像素點藥顯示的像素
- 離屏渲染。視圖的Mask,圓角,陰影。
- 半透明,GPU不得不進行數學計算,如果是不透明的,CPU隻需要取上層的就可以了
- 浮點數像素
界面頓卡的優化
建議使用成熟的”輪子”,因為作為一個開發者,你的工作是寫出高品質的App,那麼為什麼不用那些已經驗證成功的架構呢?如果真的輪子不能實作,或者你有閑下來的時間,再造輪子未嘗不可。
使用AsyncDisplayKit
使用FaceBook出品的AsyncDisplayKit來寫複雜的界面。能夠獲得異步繪制,預先加載等諸多好處。不過,需要一定的學習成本,前段時間看了下網易新聞的安裝包,就使用了AsyncDisplayKit
圖文混排引擎
大多數性能要求較高的界面就是圖文混排,比如微網誌Feed,微信朋友圈等界面。建議使用成熟的圖文混排引擎,因為這些引擎一般支援異步繪制,并且做了大量優化。推薦兩個
- YYKit
- DTCoreText
異步繪制
把複雜的界面,放到背景線程裡繪制成一個bitmap,然後再顯示。雖然有些延遲,不過換來的卻是平滑的界面。
圖檔的解碼
建議使用成熟的庫,比如SDWebImage等,能夠在背景進行圖檔解碼,減少CPU的使用。
預加載與緩存
對于複雜的TableView,可以對Cell視圖的各個控件的大小,位置背景進行預計算,并且緩存起來。這樣保證在
heightForRow
和
cellForRow
中不進行大量的計算。
盡量使用CALayer
因為Layer是一個輕量級的視圖結構,它不接受通知,不接受觸摸,不在響應鍊。是以,相對于UIView來說,它的性能較好。并且CALayer及其子類是可以使用GPU渲染的,能夠硬體加速。
圖層預合成
将兩個CALayer的内容合成到一個Bitmap裡,然後顯示。能夠減輕GPU的壓力
資料
建議看看這幾篇文章
- objc.io-繪制像素到螢幕上
- YYKit的作者:iOS 保持界面流暢的技巧
- iOS 核心動畫進階及技巧
轉載自:http://blog.csdn.net/shaobo8910/article/details/66975785