iOS 性能優化的探索

起因

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

明確優化的目標

優化的第一步,肯定是要明確我們優化具體的Case,需要達到什么樣的流暢度?是fps達到60?還是要內存使用降到一個具體的數字?
討論之后,我們最終將第一期優化定位為將首頁的fps優化到60。

調研過程

雖然說是第一期將目標定位優先優化首頁的流暢度,但是本著有第一次就要有第二次的原則,我還是將App中的其他流暢度敏感頁面也Review了一遍代碼,算是給自己留一個優化思考的方向。

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

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

PS:因為我們的IM系統是我們自己寫的,中間又經歷了公司分家,人員換了好幾茬,于是就導致了在本來架構不合理的基礎上,實現業務和功能的代碼更是屎一樣,所以IM的問題更嚴重。而且我們已經計劃了對于IM的重構時間表,所以我會在另一篇Blog里寫一下我的重構思路。

方案整理

影響流暢度的主要原因:

1、文本寬高計算、視圖布局計算
2、文本渲染、圖片解碼、圖形繪制
3、對象創建、對象調整、對象銷毀

CPU資源消耗原因以及解決辦法:

1、對象的創建:

對象的創建會分配內存、設置屬性等,會消耗CPU資源。所以盡量使用輕量對象代替,比如能用CALayer的時候盡量不用UIView,敏感位置能不用IB盡量使用純代碼手寫。

推遲同一時間創建對象,推薦使用懶加載在需要使用時候創建對象。

2、對象調整

對 UIView 的這些屬性進行調整時,消耗的資源要遠大于一般的屬性。對此你在應用中,應該盡量減少不必要的屬性修改。

當視圖層次調整時,UIView、CALayer 之間會出現很多方法調用與通知,所以在優化性能時,應該盡量避免調整視圖層次、添加和移除視圖。

3、對象銷毀

當前類持有大量對象時候,其銷毀時候的資源消耗就非常明顯。建議創建銷毀的異步隊列,將需要銷毀的對象放到隊列中銷毀。

4、布局計算

布局計算在UITableView使用中是最常見的消耗資源的地方。建議取到數據之后,異步進行計算布局并緩存下來,當復用Cell時候直接調用緩存數據。

5、AutoLayout

Autolayout 對于復雜視圖來說常常會產生嚴重的性能問題,AutoLayout相對低效的原因是隱藏在底層的命名為”Cassowary“的約束求解系統,隨著視圖數量的增長,Autolayout 帶來的 CPU 消耗會呈指數級上升,當Cell內約束超過25個的時候,會降低滑動的幀率。

具體:http://pilky.me/36/

6、文本的計算以及渲染

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

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

7、圖片解碼以及圖像的繪制

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

個最常見的地方就是 [UIView drawRect:] 里面了。由于 CoreGraphic 方法通常都是線程安全的,所以圖像的繪制可以很容易的放到后臺線程進行。

8、文件系統的調用

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

GPU資源消耗原因以及解決辦法:

1、紋理的渲染

當在較短時間顯示大量圖片時(比如 TableView 存在非常多的圖片并且快速滑動時),CPU 占用率很低,GPU 占用非常高,界面仍然會掉幀。避免這種情況的方法只能是盡量減少在短時間內大量圖片的顯示,盡可能將多張圖片合成為一張進行顯示。

2、視圖的混合(Blended)

視圖結構過于復雜,混合的過程、會消耗很多 GPU 資源。為了減輕這種情況的 GPU 消耗,應用應當盡量減少視圖數量和層次,并在不透明的視圖里標明 opaque 屬性以避免無用的 Alpha 通道合成。當然,這也可以用上面的方法,把多個視圖預先渲染為一張圖片來顯示。

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

3、圖形的生成

CALayer 的 border、圓角、陰影、遮罩(mask),CASharpLayer 的矢量圖形顯示,通常會觸發離屏渲染(offscreen rendering),而離屏渲染通常發生在 GPU 中。可以嘗試開啟 CALayer.shouldRasterize 屬性,但這會把原本離屏渲染的操作轉嫁到 CPU 上去。

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

解決方案:

1、預先計算UI布局

獲取數據之后,異步計算Cell高度以及各控件高度和位置,并儲存在CellLayouModel中,當每次Cell需要高度以及內部布局的時候就可以直接調用,不需要進行重復計算。

2、使用自動緩存高度

iOS 8之后出現了UITableView通過約束自動計算高度,但是因為iOS對于約束的算法問題,會導致流暢性降低,FDTemplateLayoutCell很好的優化了這個問題。

3、異步繪制

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

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

3、高效圖片加載

4、預加載

列表當中,當滑動到一個可以設定的位置的時候,提前獲取下載下一頁的數據,并繪制UI。詳見:https://zhuanlan.zhihu.com/p/23418800

5、針對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


另外

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,001評論 6 537
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,786評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,986評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,204評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,964評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,354評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,410評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,554評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,106評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,918評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,093評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,648評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,342評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,755評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,009評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,839評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,107評論 2 375