當前位置:
首頁 > 最新 > iOS性能優化探討

iOS性能優化探討

最近在公司內部做了一個分享會,探討了iOS上性能優化的話題,現在將重點的內容整理好發出來,各位大牛斧正。

本文將從原理出發,解釋卡頓發生的原理,然後會講解項目中行之有效的幾個優化點,最後會展望一下接下來將要嘗試的方向。下面進入正題。

屏幕顯示的原理

基本原理

屏幕顯示原理

我們知道,遠古時代的CRT顯示器的顯示原理是用電子槍掃描熒光屏來發光。如上圖所示,電子槍按照從左到右,然後從上到下的順序掃描。當電子槍換到新的一行準備進行掃描時,也就是上圖A4、B4、C4、D4的位置,顯示器會發出一個水平同步信號;而當一幀畫面繪製完成後,電子槍回復到原位準備畫下一幀前,也就是上圖D4的位置,顯示器會發出一個垂直同步信號。垂直同步信號的作用一方面是通知顯示器回到A1位置,另外一方面,也通知顯卡,準備輸出下一幀畫面。現在已經是液晶顯示器的時代了,不再使用電子槍掃描了,但是原理還是類似的,水平同步信號和垂直同步信號還是一樣被使用的。

計算機工作原理

計算機系統的工作原理如上圖所示:首先是CPU的工作,包括創建視圖分配內存、計算布局、圖片解碼以及文本繪製等;接下來輪到GPU工作了,GPU負責視圖變換、合成和渲染等;GPU渲染完提交到幀緩衝區中,等收到垂直同步信號後將幀緩衝區的內容顯示到屏幕上。

屏幕撕裂(Screen tearing)

上述的簡單的屏幕顯示原理其實會產生這樣一個問題:假設我們的顯卡速度很快,每秒生產的幀數肯定要超過顯示器刷新率。那麼在實際數據處理過程中,緩衝區的數據,在被輸出之前,就被顯卡不斷的刷新重寫。但是緩衝區並不是「先清空再寫入數據」,這太沒有效率,而是採用「新數據覆蓋老數據」的方式。

假設這樣一種情況,緩衝區已經有一副完整的幀畫面(A幀),然後顯卡生成了下一幀畫面(B幀),新一幀的數據開始寫入緩衝區,寫到一半的時候,垂直同步信號來 了,於是緩衝區的數據被輸出到顯示器。但問題是,這時緩衝區的數據,是由一半A幀和一半B幀數據合成的。因此最終顯示器上顯示出來的畫面就不是一副完整的 畫面,這就是「畫面撕裂」現象出現的原因(如下圖)。

屏幕撕裂

那怎麼才能解決畫面撕裂呢?簡單來說只要讓幀緩衝區里的數據始終保持一副完整的畫面就可以了。從技術角度出發,其實就是利用剛剛提到的垂直同步信號。

具體說起來就是,當顯卡生成了一副完整畫面並寫入了幀緩衝區之後,暫停!然後開始等待垂直同步信號,當得到垂直同步信號後,再繼續渲染下一幀寫入緩衝區。這樣就可以保證在緩衝區的數據始終是一副完整的畫面,不會出現前後幀混合的問題。

卡頓產生原因

但是呢,垂直同步帶來了一個新的問題—掉幀。所謂的掉幀,跟垂直同步有一定關係,因為垂直同步機制決定了如果在一個時鐘周期內CPU或者GPU沒有完成各自的任務的話,就會將幀緩衝區里的內容直接丟棄!掉幀並不能完全怪罪於垂直同步機制,更重要的原因是我們作為開發者沒有進行足夠的優化,將過重的任務派發到了CPU或者GPU上,下圖(from:iOS 保持界面流暢的技巧)是掉幀的圖示,表明CPU或者GPU任意一個沒能在時鐘周期內完成自己的任務的話都會導致卡頓掉幀。

卡頓圖示

行之有效的優化點

提前布局

提前布局可以說是最重要的優化點了。其實在從服務端拿到 JSON 數據的時候,關於視圖的布局就已經確定了,包括每個控制項的frame、cell的高度以及文本排版結果等等,在這個時候完全可以在後台線程計算並封裝為對應的布局對象XXXTableViewCellLayout,每個cellLayout的內存佔用並不是很多,所以直接全部緩存到內存中。當列表滾動到某個cell的時候,直接拿到對應的cellLayout配置這個cell的對應屬性即可。當然,該有的計算是免不了的,只是提前算好並緩存,免去了在滾動的時候計算和重複的計算。通過這一個優化,將本來的fps50的列表優化到了55、56左右,可以說從肉眼上已經看不出有卡頓掉幀了。

cellLayout示例圖

上圖是項目中某個cellLayout的部分代碼,可以看到裡面存的就是所有控制項的frame和文本的排版結果而已,裡面沒有任何的黑科技,只是將本來在滾動中才做的事情提前了而已。

按頁載入緩存

現狀分析:90%的APP有tableview,90%的tableview里有上拉刷新和下拉載入。以我司的項目ZAKER中的熱點新聞界面為例,簡單流程大概是這樣的:①應用啟動的時候會將磁碟中所有的新聞一次性讀取出來顯示到屏幕上; ②在每次下拉刷新和上拉載入的時候會將內存中所有新聞緩存到磁碟中,也即全量讀寫。這意味著大部分的新聞數據會反覆寫入到磁碟中,這樣的寫入是冗餘的,因為前面的這些新聞數據並沒有發生改變。

改進方案:所以優化的方法就是將這些列表數組進行分割,分割成一頁一頁,每次寫入的數據量很小,而且避免了冗餘寫入的問題。現在的流程變為:①啟動時只讀取第一批新聞顯示在屏幕中;②下拉刷新和上拉載入的時候只把當前伺服器返回的一批新聞寫入緩存中;③在上拉載入的時候會先查看磁碟中是否有未讀的緩存,若有則讀取緩存,否則才從伺服器下載一批新的文章。

直觀圖示:

按頁緩存

可以看到,優化之前整個新聞列表以及其他配置都在一個文件里,刷新10幾次之後文件大小達到2MB,並且隨著不斷刷新而越來越大;優化之後,其他的配置還是在剛剛的文件中,但是不斷增長的新聞數組被分割成一頁一頁的文件,每一頁裡面有10多條的新聞數據,同時有一個configure文件保存這些頁的信息以及頁的順序。根據測試人員的反饋,進行按頁載入緩存優化能減少5%~8%的CPU佔用,使用的內存也有一定的下降。還是有很明顯的優化效果的。

後台線程處理圖片

圓形頭像、圖片裁圓角等處理可以說是非常常見的需求了,包括從iOS11的系統各處都能看到,整體的頁面控制項都變得更加圓潤了。但是,對圖片處理必然是消耗資源的,實現過圖片圓角效果的應該都知道,最簡單的就是 layer.cornerRadius+layer.masksToBounds 的方式,但是這種做法在tableview中往往會使滾動變得卡頓,因為這種實現方式會觸發離屏渲染,屏幕外緩衝區跟當前屏幕緩衝區上下文切換是很耗性能的,所以離屏渲染往往會造成卡頓(參考:iOS 離屏渲染的研究)。

那要怎麼處理圖片呢?可以使用Core Graphics,Core Graphic通常是線程安全的,所以可以進行非同步繪製,顯示的時候再放回主線程。我們在項目中實現了一個後台處理圖片的框架,核心代碼如下:

NSBlockOperation *transformOperation = [[NSBlockOperation alloc] init];

[transformOperation addExecutionBlock:^{

// 此處處理圖片

...

dispatch_async(dispatch_get_main_queue(), ^{

// 主線程設置圖片

[self setImage:transformedImage forState:UIControlStateNormal];

}

});

}];

更加高效的控制項

還可以直接從開源庫中選用更加高效的控制項替換項目中性能沒那麼好的控制項。項目中將之前的TTTAttributedLabel、M80AttributedLabel全部替換為YYLabel,開啟YYLabel的displaysAsynchronously、ignoreCommonProperties屬性可以非同步繪製文本以及忽略不需要的屬性。更加追求性能的話,可以結合第1點的提前布局機制,在提前布局的階段生成好YYLabel渲染時用到的textLayout,顯示的時候直接賦值textLayout就可以了。

其他

還有一些比較微小的優化,對性能可以說沒有多大的影響,但是可以在開發階段稍加留意,養成良好的習慣。

盡量減少視圖層級,合併多餘的視圖。同樣以ZAKER為例,用戶顯示時的藍V標籤、達人標籤以及樓主圖片等幾個視圖,之前是用不同的view來展示的,優化過程將這幾個view合併為一個view,一個view管理這些相似的事物,也可以減少某些相同邏輯的代碼。

減少頻繁的addSubview、removeSubview,remove之後視圖的實例對象會被釋放,再add的時候會再次調用初始化函數。替代方案的話,可以用hidden屬性隱藏不顯示的視圖。

接下來的方向

非同步繪製

從一開始接觸iOS的我們就反覆被告知,UIKit的東西是絕對不能在後台線程調用的,一定得在主線程調用,所以主線程也被叫做UI線程。在後台線程調用UIKit的東西有一定幾率導致崩潰,或者出現視圖不顯示、顯示錯亂等等問題。但是呢,根據剛剛所說的,Core Graphics的那一套東西是線程安全的,所以可以通過Core Graphics在後台將視圖渲染到一張圖片上,顯示的時候在主線程將這張圖片設置到相應位置上。Facebook著名的AsyncDisplayKit的核心實現應該也是基於這個原理,接下來的優化可以嘗試這個方案。

Metal

根據Apple官方說法,Metal框架被設計用來實現兩個目標: 3D 圖形渲染和並行計算。這兩者有很多共同點。它們都在數量龐大的數據上並行運行特殊的代碼,並可以在GPU上執行。目前正在研究學習階段,看項目中是否能利用Metal進行一定的優化。

APM?

Application Performance Management(APM):應用程序性能管理, 通過對應用的可靠性、穩定性等方面的監控,進而達到可以快速修復問題、提高用戶體驗的目的。目前比較有代表性的 APM 產品有:聽雲、阿里百川、騰訊 bugly等,現在也在考慮自己研發一套APM系統,先從比較簡單的指標入手,先對卡頓和崩潰這兩個指標著手,做的順利的話再逐步擴展別的指標的檢測管理。


喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 Cocoa開發者社區 的精彩文章:

Xcode 9—進階的 iOS Simulator

TAG:Cocoa開發者社區 |