點我跳轉原文地址
卡頓的原理
iOS系統界面滑動流暢性的保持主要是依靠CPU和GPU兩大處理硬件間通力合作的結果,一個視圖的顯示需要先經過CPU創建、布局計算、對圖片解碼、文本繪制,然后CPU將計算的結果交給GPU,GPU可能需要對圖形進行變換、合成、渲染,GPU然后將渲染的結果提交到幀緩沖區等待下一次的垂直同步信號(V-Sync)到來顯示到顯示器上,如果在一個V-Sync間隔內CPU或者GPU由于過高的利用率都可能導致數據的不及時提交,那么那一幀數據就會被丟棄,等待下一次的V-Sync再顯示,這也就是通常能感受到的界面卡頓現象,也就是掉幀。
優化過程
所以當出現卡頓現象時主要去分析此刻CPU和GPU的利用率,當出現較高的CPU利用率時應及時去分析代碼執行的效率,或者用Time Profier去分析導致較高占用率的代碼,更多詳細的關于優化CPU的情況本文不做詳細分析。
而對于GPU資源消耗的優化本文將通過一個Demo優化過程來講解優化的步驟,你可以在這里GitHub下載源碼,Demo是有2個tab組成的,before是優化前視圖,after是優化后的,你可以通過對比學習體會其中的差異。
下載運行demo, 打開Xcode的調試選項,在菜單欄-Debug-View Debugging-Rendering可以找到,運行APP(真機)勾選相關的菜單選項即可
1.視圖混合(Blending)
app中顯示的效果往往是多個視圖重合疊加的效果,而在計算視圖重合顯示顏色的時候就需要考慮透明的影響,當頂部視圖出現透明的情況時,顏色的計算就需要考慮其透明度,這樣無疑增加了計算成本,消耗GPU資源,所以應盡量避免過多的透明視圖數量。
對于UIImageView而言,如果圖片本身是帶有透明通道的同樣會導致Blending,所以應該盡量避免使用帶有透明度的圖片。
對于文本UILable, 如果不設置背景顏色,同樣會出現Blending,所以需要設置UILabel的背景顏色,對于顯示中文的UILabel, 除了設置背景顏色外還需要設置masksToBounds屬性,因為中文時UILable會多了一個sublayer。
勾選Rendering中的第一個選項Color Blended Layers,此選項就是用于檢測哪些視圖出現了Blending,出現Blending的地方會用紅色標記出來,運行demo可以出現下圖所示情況:
可以看到,before tab下的控制器視圖無論是圖片還是文本都被標紅,表示出現了Blending,對于圖片我們可以在Assets里面找到對應的圖片,然后查看簡介就可以查看圖片是否有Alpha通道,會看到圖片都具有Alpha通道,也就回導致Blending
對于這種情況,可以讓UI切圖時關閉此Alpha通道,也可以直接用mac自帶的圖片軟件打開圖片,然后導出圖片關閉Alpha通道。所以我們需要將所有有Alpha的圖片都處理一遍,盡量不要使用帶有透明度的圖片。
對于UILabel,上面有提到,我們只需要下面的2行代碼即可處理:
titleLabel.backgroundColor = UIColor.white
titleLabel.layer.masksToBounds = true
通過去掉圖片alpha通道優化后如下圖所示,只有被設置為圓角的小圖片還存在Blending,因為我們是直接設置的layer的cornerRadius(IOS9以下會導致離屏渲染),同樣我們可以直接用無透明通道的圓角圖片來替換解決,但這需要UI適配更多背景圖片。
2.光柵化
開啟光柵化是通過設置屬性shouldRasterize,開啟光柵化后CALayer會被保存為bitmap放到緩存中,這樣在下次需要時可以直接中緩存中取出來顯示,這樣節省了渲染時間,例如對于設置有陰影效果的復雜視圖會對性能有一定的提升。
nameLabel.layer.shouldRasterize = true//開啟光柵化
第二個調試選項 Color Hits Green and Misses Red就是用來查看光柵化視圖的,勾選后若視圖被標記為綠色,則表示命中了緩存,直接從緩存中取出來顯示,緩存的有效時間為100ms, 而紅色則表示沒有命中。Demo中,我們對第2個Label開啟了光柵化,滾動會發現被標記為綠色
更新一個已光柵化的Layer會觸發離屏渲染,所以選擇哪些視圖適合做光柵化需要根據場景權衡,光柵化適合那些布局復雜而不經常變動的視圖,比如
- 用于避免靜態內容的復雜特效的重繪,例如UIBlurEffect
- 用于避免多個View嵌套的復雜View的重繪。
同時注意緩存是有大小限制的,所以不要過度是使用光柵化,因為超過緩存大小會導致大量的離屏渲染。
3.顏色格式
Color Copied Images選項能標識出視圖中不能被GPU處理的圖片,因為來自網絡的圖片格式可能千變萬化,有的圖片的格式是GPU無法識別的所以會交給CPU處理,出現這種情況就需要修改圖片格式,
4.不標準的表面顏色格式
Color Non-Standard Surface Formats 打開此選項后會發現Label會出現灰色的背景顏色,然后經過我們給Label設置了背景顏色后便消失了,關于此選項的相關介紹甚少,期待有人能挖掘挖掘,所以只能猜測,蘋果推薦我們給Label設置一個背景顏色。
5.顏色刷新頻率
默認情況下圖層的顏色更新是有10ms的延遲的,在某些特定情況下可能需要關閉這個延遲,但絕大多數情況用不到這個選項。
6.圖片大小
Color Misaligned Images選項在圖片像素不對齊(也就是圖片帶alpha通道)時,會在圖片上面加一層洋紅色來標識;而圖片被縮放時,會加一層黃色來標識,我們可以看到優化前的圖片會出現圖片縮放,因為圖片的顯示大小和圖片的大小不匹配。
圖片縮放同樣會消耗GPU資源,所以盡量保證圖片的顯示大小和原圖大小一致來避免縮放,所以demo的圖片在處理后大小都等于顯示的大小來避免縮放。
7.離屏渲染
Color Offscreen-Rendered Yellow會用黃色標識哪些圖層出現了離屏渲染,什么是離屏渲染?
離屏渲染表示渲染不是發生在當前屏幕的緩沖區中,而是發生在其他緩沖區的渲染,這就需要開辟更多的緩沖區,等到要用的時候再從其他的緩沖區讀取來顯示,所以這樣會消耗更多的GPU資源,所以避免離屏渲染可以有效的提升顯示性能。
如上圖所示,要顯示一個相機圖標和一個蒙層,在前兩個渲染通道中,GPU分別得到了相機的紋理和藍色的蒙版layer的渲染結果。但這兩個渲染結果沒有直接放入Frame Buffer中,這就是離屏渲染。等到第三個渲染通道,才把兩者組合起來放入Frame Buffer中最終顯示到屏幕上,這就是典型的離屏渲染。
運行demo,打開Color Offscreen-Rendered Yellow選項,在beforetab控制器視圖下出現離屏渲染的圖層便會被黃色標識出來,如圖:
可以發現右邊的大圖出現了離屏渲染,而圓角的小圖卻沒有,大圖出現離屏渲染的原因是設置了shadow陰影,而因為測試機器是ios11所以設置圖片的cornerRadius并不會導致離屏渲染。
同樣設置了以下屬性時,都會觸發離屏渲染:
- shouldRasterize(光柵化)
- masks(遮罩)
- shadows(陰影)
- edge antialiasing(抗鋸齒)
- group opacity(不透明)
- 復雜形狀設置圓角等(ios8)
- 漸變
上面出現離屏渲染的case都應該要注意,所以針對shadow可以通過設置shadowPath來避免,光柵化也應該盡量避免:
優化后代碼如下:
mainImageView.layer.shadowColor = UIColor.black.cgColormainImageView.layer.shadowOpacity = 1mainImageView.layer.shadowRadius = 2
// mainImageView.layer.shadowOffset = CGSize(width: 2, height: 2)mainImageView.layer.shadowPath = UIBezierPath.init(roundedRect: mainImageView.bounds, cornerRadius: 2).cgPathsmallImageView.layer.cornerRadius = smallImageView.frame.size.width / 2smallImageView.clipsToBounds = truenameLabel.backgroundColor = UIColor.whitetitleLabel.backgroundColor = UIColor.whitetitleLabel.layer.masksToBounds = truenameLabel.layer.masksToBounds = true
// nameLabel.layer.shouldRasterize = true
8.快速路徑
第七個選項“Color Compositing Fast-Path Blue”用于標記由硬件繪制的路徑,藍色越多越好,demo略
9.變化區域
最后一個Flash Updated Regions 用于標記發生重繪制的區域并用黃色標記出來,對于大多數不變的區域應該盡量的避免重繪而只有小部分經常變化的區域重繪這有助于顯著提高性能。demo略
優化結果
經過對圖片Alpha透明度調整,大小剪裁適配顯示,更改設置陰影,文本背景等一系列的優化,使用Core Animation記錄GPU使用率的變化來觀察優化效果:
before:
可以看到優化前CPU平均占用率達到60%,這還只是簡單的視圖布局,如果更加復雜占用率還會增加,這也就意味著更高的卡頓風險。
after:
對比可以發現在提升了平均顯示幀數的同時大大降低了GPU消耗,性能得到顯著的提升。
測試環境:Xcode9.3、iPhone6s、 iOS11、Swift4
Demo地址:GitHub地址