起因
我們公司的主App在大約17年5月份前后經(jīng)歷了一次大版本迭代隐解,迭代之后更換了若干個(gè)一級(jí)和二級(jí)頁面退渗,首頁就在這些個(gè)一級(jí)頁面之內(nèi)今艺。
17年大約11月份的時(shí)候挺身,我們的小程序第一個(gè)版本正式上線,然后我們技術(shù)的大Leader拿來了小程序給我們看看消略,小程序的首頁流暢性確實(shí)優(yōu)于我們客戶端堡称,于是我們正式啟動(dòng)了性能優(yōu)化。
明確優(yōu)化的目標(biāo)
優(yōu)化的第一步艺演,肯定是要明確我們優(yōu)化具體的Case却紧,需要達(dá)到什么樣的流暢度婿失?是fps達(dá)到60?還是要內(nèi)存使用降到一個(gè)具體的數(shù)字啄寡?
討論之后豪硅,我們最終將第一期優(yōu)化定位為將首頁的fps優(yōu)化到60。
調(diào)研過程
雖然說是第一期將目標(biāo)定位優(yōu)先優(yōu)化首頁的流暢度挺物,但是本著有第一次就要有第二次的原則懒浮,我還是將App中的其他流暢度敏感頁面也Review了一遍代碼,算是給自己留一個(gè)優(yōu)化思考的方向识藤。
Review代碼發(fā)現(xiàn)一些比較顯而易見的問題:
- 肆意調(diào)用數(shù)據(jù)庫而沒有用cache
- 復(fù)雜UI大量使用約束
- 離屏渲染
- 像素混合
PS:因?yàn)槲覀兊腎M系統(tǒng)是我們自己寫的砚著,中間又經(jīng)歷了公司分家,人員換了好幾茬痴昧,于是就導(dǎo)致了在本來架構(gòu)不合理的基礎(chǔ)上稽穆,實(shí)現(xiàn)業(yè)務(wù)和功能的代碼更是屎一樣,所以IM的問題更嚴(yán)重赶撰。而且我們已經(jīng)計(jì)劃了對(duì)于IM的重構(gòu)時(shí)間表舌镶,所以我會(huì)在另一篇Blog里寫一下我的重構(gòu)思路。
方案整理
影響流暢度的主要原因:
1豪娜、文本寬高計(jì)算餐胀、視圖布局計(jì)算
2、文本渲染瘤载、圖片解碼否灾、圖形繪制
3、對(duì)象創(chuàng)建鸣奔、對(duì)象調(diào)整墨技、對(duì)象銷毀
CPU資源消耗原因以及解決辦法:
1、對(duì)象的創(chuàng)建:
對(duì)象的創(chuàng)建會(huì)分配內(nèi)存挎狸、設(shè)置屬性等扣汪,會(huì)消耗CPU資源。所以盡量使用輕量對(duì)象代替伟叛,比如能用CALayer的時(shí)候盡量不用UIView私痹,敏感位置能不用IB盡量使用純代碼手寫脐嫂。
推遲同一時(shí)間創(chuàng)建對(duì)象统刮,推薦使用懶加載在需要使用時(shí)候創(chuàng)建對(duì)象。
2账千、對(duì)象調(diào)整
對(duì) UIView 的這些屬性進(jìn)行調(diào)整時(shí)侥蒙,消耗的資源要遠(yuǎn)大于一般的屬性。對(duì)此你在應(yīng)用中匀奏,應(yīng)該盡量減少不必要的屬性修改鞭衩。
當(dāng)視圖層次調(diào)整時(shí),UIView、CALayer 之間會(huì)出現(xiàn)很多方法調(diào)用與通知论衍,所以在優(yōu)化性能時(shí)瑞佩,應(yīng)該盡量避免調(diào)整視圖層次、添加和移除視圖坯台。
3炬丸、對(duì)象銷毀
當(dāng)前類持有大量對(duì)象時(shí)候,其銷毀時(shí)候的資源消耗就非常明顯蜒蕾。建議創(chuàng)建銷毀的異步隊(duì)列稠炬,將需要銷毀的對(duì)象放到隊(duì)列中銷毀。
4咪啡、布局計(jì)算
布局計(jì)算在UITableView使用中是最常見的消耗資源的地方首启。建議取到數(shù)據(jù)之后,異步進(jìn)行計(jì)算布局并緩存下來撤摸,當(dāng)復(fù)用Cell時(shí)候直接調(diào)用緩存數(shù)據(jù)毅桃。
5、AutoLayout
Autolayout 對(duì)于復(fù)雜視圖來說常常會(huì)產(chǎn)生嚴(yán)重的性能問題准夷,AutoLayout相對(duì)低效的原因是隱藏在底層的命名為”Cassowary“的約束求解系統(tǒng)疾嗅,隨著視圖數(shù)量的增長,Autolayout 帶來的 CPU 消耗會(huì)呈指數(shù)級(jí)上升冕象,當(dāng)Cell內(nèi)約束超過25個(gè)的時(shí)候代承,會(huì)降低滑動(dòng)的幀率。
具體:http://pilky.me/36/渐扮。
6论悴、文本的計(jì)算以及渲染
UI中存在大量的對(duì)于文本高度的適配,可以參考:用 [NSAttributedString boundingRectWithSize:options:context:] 來計(jì)算文本寬高墓律,用 -[NSAttributedString drawWithRect:options:context:] 來繪制文本膀估。盡管這兩個(gè)方法性能不錯(cuò),但仍舊需要放到后臺(tái)線程進(jìn)行以避免阻塞主線程耻讽。
常見的文本控件 (UILabel察纯、UITextView 等),其排版和繪制都是在主線程進(jìn)行的针肥,當(dāng)顯示大量文本時(shí)饼记,CPU 的壓力會(huì)非常大。解決辦法是利用TextKit或者是CoreText自定義文本控件慰枕。詳見:YYText具则。
7、圖片解碼以及圖像的繪制
當(dāng)你用 UIImage 或 CGImageSource 的那幾個(gè)方法創(chuàng)建圖片時(shí)具帮,圖片數(shù)據(jù)并不會(huì)立刻解碼博肋。圖片設(shè)置到 UIImageView 或者 CALayer.contents 中去低斋,并且 CALayer 被提交到 GPU 前,CGImage 中的數(shù)據(jù)才會(huì)得到解碼匪凡。這一步是發(fā)生在主線程的膊畴,并且不可避免。如果想要繞開這個(gè)機(jī)制病游,常見的做法是在后臺(tái)線程先把圖片繪制到 CGBitmapContext 中巴比,然后從 Bitmap 直接創(chuàng)建圖片。目前常見的網(wǎng)絡(luò)圖片庫都自帶這個(gè)功能礁遵。
個(gè)最常見的地方就是 [UIView drawRect:] 里面了轻绞。由于 CoreGraphic 方法通常都是線程安全的,所以圖像的繪制可以很容易的放到后臺(tái)線程進(jìn)行佣耐。
8政勃、文件系統(tǒng)的調(diào)用
NSFileManager獲取一個(gè)目錄獲取文件信息,進(jìn)行多次遞歸計(jì)算兼砖,stat幾乎瞬間完成奸远,NSFileManager耗時(shí)較長且消耗CPU。
GPU資源消耗原因以及解決辦法:
1讽挟、紋理的渲染
當(dāng)在較短時(shí)間顯示大量圖片時(shí)(比如 TableView 存在非常多的圖片并且快速滑動(dòng)時(shí))懒叛,CPU 占用率很低,GPU 占用非常高耽梅,界面仍然會(huì)掉幀薛窥。避免這種情況的方法只能是盡量減少在短時(shí)間內(nèi)大量圖片的顯示,盡可能將多張圖片合成為一張進(jìn)行顯示眼姐。
2诅迷、視圖的混合(Blended)
視圖結(jié)構(gòu)過于復(fù)雜,混合的過程众旗、會(huì)消耗很多 GPU 資源罢杉。為了減輕這種情況的 GPU 消耗,應(yīng)用應(yīng)當(dāng)盡量減少視圖數(shù)量和層次贡歧,并在不透明的視圖里標(biāo)明 opaque 屬性以避免無用的 Alpha 通道合成滩租。當(dāng)然,這也可以用上面的方法利朵,把多個(gè)視圖預(yù)先渲染為一張圖片來顯示律想。
- Blended Layers(視圖混合):在同一個(gè)區(qū)域內(nèi),存在著多個(gè)有透明度的圖層哗咆,那么GPU需要更多的計(jì)算蜘欲,混合上下多個(gè)圖層才能得出最終像素的RGB值益眉。
- Misaligned Images(像素對(duì)齊):邏輯像素(point)和 物理像素(pixel)無法相匹配晌柬;圖片的size和顯示圖片的imageView的size(邏輯像素(point))不相等姥份。
3、圖形的生成
CALayer 的 border年碘、圓角澈歉、陰影、遮罩(mask)屿衅,CASharpLayer 的矢量圖形顯示埃难,通常會(huì)觸發(fā)離屏渲染(offscreen rendering),而離屏渲染通常發(fā)生在 GPU 中涤久∥谐荆可以嘗試開啟 CALayer.shouldRasterize 屬性,但這會(huì)把原本離屏渲染的操作轉(zhuǎn)嫁到 CPU 上去响迂。
好的方法是使用圖片遮罩等方法考抄,避免使用圓角和隱形等。詳細(xì):iOS的離屏渲染
解決方案:
1蔗彤、預(yù)先計(jì)算UI布局
獲取數(shù)據(jù)之后川梅,異步計(jì)算Cell高度以及各控件高度和位置,并儲(chǔ)存在CellLayouModel中然遏,當(dāng)每次Cell需要高度以及內(nèi)部布局的時(shí)候就可以直接調(diào)用贫途,不需要進(jìn)行重復(fù)計(jì)算。
2待侵、使用自動(dòng)緩存高度
iOS 8之后出現(xiàn)了UITableView通過約束自動(dòng)計(jì)算高度丢早,但是因?yàn)閕OS對(duì)于約束的算法問題,會(huì)導(dǎo)致流暢性降低秧倾,FDTemplateLayoutCell很好的優(yōu)化了這個(gè)問題香拉。
3、異步繪制
Facebook的開源項(xiàng)目Texture(原AsyncDisplayKit)中狂,通過利用ASDisplayNode封裝了CALayer凫碌,實(shí)現(xiàn)了異步繪制。
第三方微博客戶端墨客的是現(xiàn)實(shí)胃榕,當(dāng)滑動(dòng)時(shí)盛险,松開手指后,立刻計(jì)算出滑動(dòng)停止時(shí) Cell 的位置勋又,并預(yù)先繪制那個(gè)位置附近的幾個(gè) Cell苦掘,而忽略當(dāng)前滑動(dòng)中的 Cell。但也有缺點(diǎn)楔壤,快速滑動(dòng)的時(shí)候有可能會(huì)出現(xiàn)大量空白鹤啡。
3、高效圖片加載
4蹲嚣、預(yù)加載
列表當(dāng)中递瑰,當(dāng)滑動(dòng)到一個(gè)可以設(shè)定的位置的時(shí)候祟牲,提前獲取下載下一頁的數(shù)據(jù),并繪制UI抖部。詳見:https://zhuanlan.zhihu.com/p/23418800说贝。
5、針對(duì)Blended Layers以及Misaligned Images
Blended Layers:
- 盡量少的使用具有透明色的圖片
- 盡量多的將UI部件增加背景色
- 減少同一像素點(diǎn)進(jìn)行過多的顏色計(jì)算
Misaligned Images:
現(xiàn)象:
洋紅色:UIView的frame像素不對(duì)齊慎颗,即不能換算成整數(shù)像素值乡恕。
黃色:UIImageView的圖片像素大小與其frame.size不對(duì)齊,圖片發(fā)生了縮放造成俯萎。
解決:
- 盡量使用ceil()傲宜,保證沒有小數(shù)的UI繪制
- 盡量不實(shí)用0.01f標(biāo)記UITableView或者UICollectionView的header以及footer
- 網(wǎng)絡(luò)上獲取的圖片沒有@2x和@3x的區(qū)別,需要我們縮放圖片到與UIImageView對(duì)應(yīng)的尺寸夫啊,且縮放后的圖片的scale和[UIScreen mainScreen].scale相等蛋哭,再顯示出來。
其他:
下面的情況或操作會(huì)引發(fā)離屏渲染:
- 為圖層設(shè)置遮罩(layer.mask)
- 將圖層的layer.masksToBounds / view.clipsToBounds屬性設(shè)置為true
- 將圖層layer.allowsGroupOpacity屬性設(shè)置為YES和layer.opacity小于1.0
- 為圖層設(shè)置陰影(layer.shadow *)涮母。
- 為圖層設(shè)置layer.shouldRasterize=true
- 具有l(wèi)ayer.cornerRadius谆趾,layer.edgeAntialiasingMask,layer.allowsEdgeAntialiasing的圖層
- 文本(任何種類叛本,包括UILabel沪蓬,CATextLayer,Core Text等)来候。
- 使用CGContext在drawRect :方法中繪制大部分情況下會(huì)導(dǎo)致離屏渲染跷叉,甚至僅僅是一個(gè)空的實(shí)現(xiàn)。
開工
我們綜合分析了下現(xiàn)有首頁代碼的代碼結(jié)構(gòu)营搅,發(fā)現(xiàn)主要存在問題如下
- 較多的使用約束進(jìn)行布局
- 每次Cell滑動(dòng)進(jìn)入屏幕都需要根據(jù)DataSource計(jì)算一次Cell高度云挟,浪費(fèi)時(shí)間。
- 像素混合等問題嚴(yán)重转质。
- 存在離屏渲染园欣。
- 無預(yù)加載邏輯,導(dǎo)致滑動(dòng)流暢性降低休蟹。
于是我們做了以下措施:
- 使用frame布局來替換約束布局沸枯。
- 每次拉取到數(shù)據(jù)之后,異步計(jì)算Cell高度并做為單獨(dú)字段緩存在DataSource中赂弓。
- 按照代碼規(guī)范绑榴,規(guī)避像素混合問題和離屏渲染。
- 在相應(yīng)位置增加了預(yù)先拉取數(shù)據(jù)的邏輯盈魁,實(shí)現(xiàn)效果是用戶沒有將UI滑動(dòng)到最后一條的時(shí)候翔怎,數(shù)據(jù)以及完成了拉取并刷新UI的邏輯。
總結(jié)
性能優(yōu)化這個(gè)東西其實(shí)很難形成一個(gè)具體的方案,為什么這么說赤套?因?yàn)橹苑Q之為優(yōu)化飘痛,是因?yàn)橐谠械拇a基礎(chǔ)上進(jìn)行優(yōu)化,原有的代碼又有各式各樣的原因?qū)е滦枰勒宅F(xiàn)有代碼來優(yōu)化于毙,而很難完全脫離現(xiàn)有的情況完全參照某一種的既定方案進(jìn)行優(yōu)化敦冬。假如說是完全參照某一種的方案優(yōu)化的話辅搬,建議還是將某一個(gè)性能敏感的頁面利用Texture進(jìn)行完全重寫唯沮,這樣才能算是整體化一的利用了某一種方案。
Refrence
- 如何做優(yōu)化堪遂,UITabelView才能更加順滑
- iOS 保持界面流暢的技巧
- 優(yōu)化UITableViewCell高度計(jì)算的那些事
- iOS優(yōu)化(三)沒錯(cuò)我還是滑動(dòng)優(yōu)化
- iOS的離屏渲染
- iOS開發(fā)針對(duì)對(duì)Masonry下的FPS優(yōu)化討論