iOS開發 容易忽略的幀率殺手:陰影

Bruce_Liu發表於2019-03-13

原文連結

離屏渲染

  關於 UITableView、UICollectionView 滑動卡頓,大家都會想起一個大殺手:圓角。

  說起圓角,其實是針對 UITableView、UICollectionView 等列表檢視 Cell 及其子檢視中包含的需要重複大量切圓角的場景,關於這個問題當時在開發圈裡也曾經引起過熱議,解決方案也挺多,這裡就不再過多的去討論。

  不過有一點值得注意的是,可能是蘋果也意識到離屏渲染會產生效能問題,所以 iOS9 及之後的 iOS 版本對 UIImageView 離屏渲染做了一些優化。

iOS 版本上的優化

  • iOS 9.0 之前 UIImageView 跟 UIButton 設定圓角都會觸發離屏渲染。

  • iOS 9.0 之後 UIButton 設定圓角會觸發離屏渲染,而 UIImageView 裡 png 圖片設定圓角不會觸發離屏渲染了,但是其他型別的圖片依然會觸發離屏渲染,而且如果設定其他陰影效果之類的也是會觸發離屏渲染的。

Instruments 監測離屏渲染

Instruments 的 Core Animation 工具中有幾個和離屏渲染相關的檢查選項:

  • Color Offscreen-Rendered Yellow 開啟後會把那些需要離屏渲染的圖層高亮成黃色,這就意味著黃色圖層可能存在效能問題。
  • Color Hits Green and Misses Red 如果 shouldRasterize 被設定成 YES,對應的渲染結果會被快取,如果圖層是綠色,就表示這些快取被複用;如果是紅色就表示快取會被重複建立,這就表示該處存在效能問題了。

注意:Xcode9.3之後的版本功能上做的調整,這些選項已經直接整合到 Xcode 裡面了

iOS開發 容易忽略的幀率殺手:陰影

繪製陰影造成的效能問題

  不過我們今天主角並不是圓角,不過殺傷力並不亞於圓角,他的名字叫做陰影

  其實這個東西平常都是設計同學或者產品同學,為了增強頁面的視覺效果而新增的。

  一般的實現方式是這樣的:

// 這裡我們只是給 cell 新增陰影效果,其他什麼都不做,看看陰影對裝置效能的影響
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {

    let cell: UICollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: ViewController.cellId, for: indexPath)
    cell.backgroundColor = UIColor.gray
    cell.layer.shadowOffset = CGSize(width: 0, height: 2)
    cell.layer.shadowOpacity = 0.5

    return cell
}
複製程式碼

  開啟 Xcode 中監測離屏渲染的選項之後,展示在介面上是這個效果(Cell全都觸發了離屏渲染):

iOS開發 容易忽略的幀率殺手:陰影

  然後我們按 Command+I 快捷鍵進入 Profile,選擇 CoreAnimation 檢測幀率及CPU、GPU利用率(本文所用的 iPhone 裝置為 iPhone6s,系統版本為 iOS12.0.1)。

開啟錄製之後,操作也很簡單,就只是簡單的上下滑動列表。

  可以看到監測到的資料如下:

iOS開發 容易忽略的幀率殺手:陰影

  其實從手機端的體驗感覺還說的過去,幀率基本能達到50fps,卡頓感比較弱,大部分使用者還是可以接受的。但是這裡比較致命的問題是:1. 我們的列表裡還沒有填充資料,僅僅是加了個陰影而已啊,加入了業務邏輯後,幀率肯定會進一步降低。2. GPU 使用率高的可怕,一般情況下,移動裝置上面幾個耗電大戶無非就是 CPU、GPU、WiFi 模組,GPU 佔用率這麼高,使用者最直觀的感受就是:我的手機是不是該換電池了???這對於一個高使用率的 App 來說簡直是不能接受的。

  

  發現問題之後,其實解決方案也很簡單:將 Cell 的 layer 進行光柵化。雖然光柵化也會觸發離屏渲染,但是光柵化會將陰影合成到圖片中,cell 重用的時候直接載入圖片即可,不必每次都繪製陰影,從而相對的提升效能。

cell.layer.shouldRasterize = true
複製程式碼

  不過單獨設定這個屬性是不夠的,因為預設情況下,光柵化生成的圖片是 @1x 的,而現在 iOS 裝置顯示器基本都是 @2x 或者 @3x,這樣就會造成顯示模糊,鋸齒感強烈,影響視覺效果。

  layer 有一個屬性就是專門解決這個問題的:

/* The scale at which the layer will be rasterized (when the
 * shouldRasterize property has been set to YES) relative to the
 * coordinate space of the layer. Defaults to one. Animatable. */

open var rasterizationScale: CGFloat
複製程式碼

  那麼要解決這個問題,只需要設定光柵化的預設縮放係數為螢幕顯示倍率即可:

cell.layer.rasterizationScale = UIScreen.main.scale
複製程式碼

  更改完之後,我們開啟 Xcode 中監測離屏渲染的選項,把專案執行到裝置上檢視效果,這次已經沒有明顯的離屏渲染了(光柵化觸發離屏渲染的過程很短,估計沒有捕捉到):

iOS開發 容易忽略的幀率殺手:陰影

  之後我們再次進入 Profile,檢視更改完之後的效果:

iOS開發 容易忽略的幀率殺手:陰影

  介面上顯示效果和修改之前一樣,滑動幀率有所提升,重要的是 GPU 利用率大大降低了,使用者最直觀的體驗就是裝置續航體驗更加友好了,再也不會被用4000ma·h大電池手機的朋友嘲笑電池一天幾衝了 [捂臉]。

  至此,UICollectionViewCell 陰影效果造成列表滑動卡頓的問題就得以解決了,方案雖然很簡單,還是需要我們在日常開發中多注意這類容易引發效能問題的點,發現問題之後能夠及時的優化。做好使用者體驗,才能開發出更加優秀 的 App!

相關文章