iOS 性能優化的探索

原由

咱們公司的主App在大約17年5月份先後經歷了一次大版本迭代,迭代以後更換了若干個一級和二級頁面,首頁就在這些個一級頁面以內。 17年大約11月份的時候,咱們的小程序第一個版本正式上線,而後咱們技術的大Leader拿來了小程序給咱們看看,小程序的首頁流暢性確實優於咱們客戶端,因而咱們正式啓動了性能優化。ios

明確優化的目標

優化的第一步,確定是要明確咱們優化具體的Case,須要達到什麼樣的流暢度?是fps達到60?仍是要內存使用降到一個具體的數字? 討論以後,咱們最終將第一期優化定位爲將首頁的fps優化到60。git

調研過程

雖說是第一期將目標定位優先優化首頁的流暢度,可是本着有第一次就要有第二次的原則,我仍是將App中的其餘流暢度敏感頁面也Review了一遍代碼,算是給本身留一個優化思考的方向。github

Review代碼發現一些比較顯而易見的問題:算法

  • 肆意調用數據庫而沒有用cache
  • 複雜UI大量使用約束
  • 離屏渲染
  • 像素混合

PS:由於咱們的IM系統是咱們本身寫的,中間又經歷了公司分家,人員換了好幾茬,因而就致使了在原本架構不合理的基礎上,實現業務和功能的代碼更是屎同樣,因此IM的問題更嚴重。並且咱們已經計劃了對於IM的重構時間表,因此我會在另外一篇Blog裏寫一下個人重構思路。數據庫

方案整理

影響流暢度的主要緣由:

一、文本寬高計算、視圖佈局計算 二、文本渲染、圖片解碼、圖形繪製 三、對象建立、對象調整、對象銷燬小程序

CPU資源消耗緣由以及解決辦法:

一、對象的建立:緩存

對象的建立會分配內存、設置屬性等,會消耗CPU資源。因此儘可能使用輕量對象代替,好比能用CALayer的時候儘可能不用UIView,敏感位置能不用IB儘可能使用純代碼手寫。安全

推遲同一時間建立對象,推薦使用懶加載在須要使用時候建立對象。性能優化

二、對象調整網絡

對 UIView 的這些屬性進行調整時,消耗的資源要遠大於通常的屬性。對此你在應用中,應該儘可能減小沒必要要的屬性修改。

當視圖層次調整時,UIView、CALayer 之間會出現不少方法調用與通知,因此在優化性能時,應該儘可能避免調整視圖層次、添加和移除視圖。

三、對象銷燬

當前類持有大量對象時候,其銷燬時候的資源消耗就很是明顯。建議建立銷燬的異步隊列,將須要銷燬的對象放到隊列中銷燬。

四、佈局計算

佈局計算在UITableView使用中是最多見的消耗資源的地方。建議取到數據以後,異步進行計算佈局並緩存下來,當複用Cell時候直接調用緩存數據。

五、AutoLayout

Autolayout 對於複雜視圖來講經常會產生嚴重的性能問題,AutoLayout相對低效的緣由是隱藏在底層的命名爲」Cassowary「的約束求解系統,隨着視圖數量的增加,Autolayout 帶來的 CPU 消耗會呈指數級上升,當Cell內約束超過25個的時候,會下降滑動的幀率。

具體:pilky.me/36/

六、文本的計算以及渲染

UI中存在大量的對於文本高度的適配,能夠參考:用 [NSAttributedString boundingRectWithSize:options:context:] 來計算文本寬高,用 -[NSAttributedString drawWithRect:options:context:] 來繪製文本。儘管這兩個方法性能不錯,但仍舊須要放到後臺線程進行以免阻塞主線程。

常見的文本控件 (UILabel、UITextView 等),其排版和繪製都是在主線程進行的,當顯示大量文本時,CPU 的壓力會很是大。解決辦法是利用TextKit或者是CoreText自定義文本控件。詳見:YYText

七、圖片解碼以及圖像的繪製

當你用 UIImage 或 CGImageSource 的那幾個方法建立圖片時,圖片數據並不會馬上解碼。圖片設置到 UIImageView 或者 CALayer.contents 中去,而且 CALayer 被提交到 GPU 前,CGImage 中的數據纔會獲得解碼。這一步是發生在主線程的,而且不可避免。若是想要繞開這個機制,常見的作法是在後臺線程先把圖片繪製到 CGBitmapContext 中,而後從 Bitmap 直接建立圖片。目前常見的網絡圖片庫都自帶這個功能。

個最多見的地方就是 [UIView drawRect:] 裏面了。因爲 CoreGraphic 方法一般都是線程安全的,因此圖像的繪製能夠很容易的放到後臺線程進行。

八、文件系統的調用

NSFileManager獲取一個目錄獲取文件信息,進行屢次遞歸計算,stat幾乎瞬間完成,NSFileManager耗時較長且消耗CPU。

GPU資源消耗緣由以及解決辦法:

一、紋理的渲染

當在較短期顯示大量圖片時(好比 TableView 存在很是多的圖片而且快速滑動時),CPU 佔用率很低,GPU 佔用很是高,界面仍然會掉幀。避免這種狀況的方法只能是儘可能減小在短期內大量圖片的顯示,儘量將多張圖片合成爲一張進行顯示。

二、視圖的混合(Blended)

視圖結構過於複雜,混合的過程、會消耗不少 GPU 資源。爲了減輕這種狀況的 GPU 消耗,應用應當儘可能減小視圖數量和層次,並在不透明的視圖裏標明 opaque 屬性以免無用的 Alpha 通道合成。固然,這也能夠用上面的方法,把多個視圖預先渲染爲一張圖片來顯示。

  1. Blended Layers(視圖混合):在同一個區域內,存在着多個有透明度的圖層,那麼GPU須要更多的計算,混合上下多個圖層才能得出最終像素的RGB值。
  2. Misaligned Images(像素對齊):邏輯像素(point)和 物理像素(pixel)沒法相匹配;圖片的size和顯示圖片的imageView的size(邏輯像素(point))不相等。

三、圖形的生成

CALayer 的 border、圓角、陰影、遮罩(mask),CASharpLayer 的矢量圖形顯示,一般會觸發離屏渲染(offscreen rendering),而離屏渲染一般發生在 GPU 中。能夠嘗試開啓 CALayer.shouldRasterize 屬性,但這會把本來離屏渲染的操做轉嫁到 CPU 上去。

好的方法是使用圖片遮罩等方法,避免使用圓角和隱形等。詳細:iOS的離屏渲染

解決方案:

一、預先計算UI佈局

獲取數據以後,異步計算Cell高度以及各控件高度和位置,並儲存在CellLayouModel中,當每次Cell須要高度以及內部佈局的時候就能夠直接調用,不須要進行重複計算。

二、使用自動緩存高度

iOS 8以後出現了UITableView經過約束自動計算高度,可是由於iOS對於約束的算法問題,會致使流暢性下降,FDTemplateLayoutCell很好的優化了這個問題。

三、異步繪製

Facebook的開源項目Texture(原AsyncDisplayKit),經過利用ASDisplayNode封裝了CALayer,實現了異步繪製。

第三方微博客戶端墨客的是現實,當滑動時,鬆開手指後,馬上計算出滑動中止時 Cell 的位置,並預先繪製那個位置附近的幾個 Cell,而忽略當前滑動中的 Cell。但也有缺點,快速滑動的時候有可能會出現大量空白。

三、高效圖片加載

四、預加載

列表當中,當滑動到一個能夠設定的位置的時候,提早獲取下載下一頁的數據,並繪製UI。詳見:zhuanlan.zhihu.com/p/23418800

五、針對Blended Layers以及Misaligned Images

Blended Layers:

  1. 儘可能少的使用具備透明色的圖片
  2. 儘可能多的將UI部件增長背景色
  3. 減小同一像素點進行過多的顏色計算

Misaligned Images:

現象:

洋紅色:UIView的frame像素不對齊,即不能換算成整數像素值。 黃色:UIImageView的圖片像素大小與其frame.size不對齊,圖片發生了縮放形成。

解決:

  1. 儘可能使用ceil(),保證沒有小數的UI繪製
  2. 儘可能不實用0.01f標記UITableView或者UICollectionView的header以及footer
  3. 網絡上獲取的圖片沒有@2x和@3x的區別,須要咱們縮放圖片到與UIImageView對應的尺寸,且縮放後的圖片的scale和[UIScreen mainScreen].scale相等,再顯示出來。

其餘:

下面的狀況或操做會引起離屏渲染:

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

開工

咱們綜合分析了下現有首頁代碼的代碼結構,發現主要存在問題以下

  • 較多的使用約束進行佈局
  • 每次Cell滑動進入屏幕都須要根據DataSource計算一次Cell高度,浪費時間。
  • 像素混合等問題嚴重。
  • 存在離屏渲染。
  • 無預加載邏輯,致使滑動流暢性下降。

因而咱們作了如下措施:

  • 使用frame佈局來替換約束佈局。
  • 每次拉取到數據以後,異步計算Cell高度並作爲單獨字段緩存在DataSource中。
  • 按照代碼規範,規避像素混合問題和離屏渲染。
  • 在相應位置增長了預先拉取數據的邏輯,實現效果是用戶沒有將UI滑動到最後一條的時候,數據以及完成了拉取並刷新UI的邏輯。

總結

性能優化這個東西其實很難造成一個具體的方案,爲何這麼說?由於之因此稱之爲優化,是由於要在原有的代碼基礎上進行優化,原有的代碼又有各式各樣的緣由致使須要依照現有代碼來優化,而很難徹底脫離現有的狀況徹底參照某一種的既定方案進行優化。假如說是徹底參照某一種的方案優化的話,建議仍是將某一個性能敏感的頁面利用Texture進行徹底重寫,這樣才能算是總體化一的利用了某一種方案。


Refrence


另外

相關文章
相關標籤/搜索