近期把滑動優(yōu)化的一些經(jīng)驗整理了一下澄成,在公司做了一次技術(shù)分享,和我之前的文章有一小部分重疊∥废牛現(xiàn)摘要如下环揽,希望大家不吝賜教,共同討論進(jìn)步庵佣。
一.滑動優(yōu)化的玄學(xué)
為什么說是玄學(xué)呢,因為大部分情況下的APP汛兜,用不到這些優(yōu)化的點巴粪,過早的優(yōu)化是惡魔,當(dāng)真正出現(xiàn)性能問題的時候,再考慮這些方面的優(yōu)化肛根。
1.多個透明元素重疊顯示的性能問題辫塌。
- 解決方案:合并成一張圖顯示
- 原理:CPU方面,減少了UIKit的創(chuàng)建消耗派哲,GPU方面臼氨,避免了合成渲染產(chǎn)生的消耗。
- AsyncDisplayKit(現(xiàn)在叫Texture)芭届,針對多個透明元素的重疊储矩,預(yù)合并無點擊響應(yīng),不改變動畫的圖層褂乍。
- Texture的保持流暢的原理:UIKit不是線程安全的持隧,所以必須在主線程改動。Texture利用中間變量存儲改動逃片,保證線程安全屡拨,在合適的機(jī)會將并發(fā)操作同步到主線程。
- 暫時不用Texture的原因:需要用Texture Node Container替換UIKit元素褥实,成本較大呀狼。
2.靜態(tài)cell、多圖待加載的優(yōu)化
- 解決方案:合并成一張圖顯示损离;
- 原理:提升I/O速度哥艇,一個大文件的讀取速度,通常比多個小文件要快草冈。
3.展示適合界面尺寸圖片她奥,不進(jìn)行拉伸縮放。
- 解決方案:從服務(wù)器拉取合適尺寸的圖片(例如七牛的服務(wù)就帶裁剪/壓縮參數(shù))怎棱;
- 原理:過大圖片對內(nèi)存消耗巨大(圖片占用內(nèi)存 = 圖像高×圖像寬×像素位數(shù))哩俭;不符合UIImageView尺寸的圖片,進(jìn)行重新縮減/放大尺寸的消耗是非常巨大的拳恋。
4.imageNamed和imageWithContentsOfFile
這個知道的人比較多凡资,因為緩存圖片的消耗通常是肉眼可見的多。
常用的元素例如icon之類的谬运,采用imageNamed:隙赁,系統(tǒng)會有緩存。
如果是較大或者不常用的圖片資源梆暖,采用imageWithContentsOfFile:伞访。
5.減少autolayout的使用
- 解決方案:頁面元素多的時候,減少autolayout布局轰驳,采用frame厚掷。
- 原理:元素多時弟灼,autolayout的消耗非常驚人(http://pilky.me/36/) ,之前看過搜狗的iOS分享冒黑,搜狗輸入法鍵盤彈出狂卡即是此原因田绑;
6.獲取文件大小
- 解決方案:不要使用NSFileManager,用C的stat來獲取文件信息抡爹。
- 實例:獲取一個目錄下所有文件大小掩驱,進(jìn)行多次遞歸計算,stat幾乎瞬間完成冬竟,NSFileManager耗時較長欧穴。
7.NSDateFormatter產(chǎn)生較大消耗
- 解決方案:.緩存NSDateFormatter結(jié)果,不多次創(chuàng)建诱咏,及時釋放苔可。
- 做過類似日歷的同學(xué)應(yīng)該都懂??
8.圖片解碼:
- 解決方案:CALayer 被提交到 GPU 前,CGImage 中的數(shù)據(jù)才會得到解碼袋狞,GPU執(zhí)行焚辅,卡主線程。常見的做法是在后臺線程先把圖片繪制到 CGBitmapContext 中苟鸯,然后從 Bitmap 直接創(chuàng)建圖片同蜻。
- SDWebImage/YYImage等圖片庫都是這么做的,有興趣的同學(xué)可以去看下源碼早处。如果你是自己做圖片下載湾蔓,就要考慮到相關(guān)優(yōu)化。
二.cell高度預(yù)計算/緩存
- 一般情況下砌梆,不要用estimatedRowHeight默责,不然容易鬼畜;
- systemLayoutSizeFittingSize:這個方法咸包,就是大部分cell布局庫采用的方法桃序,只要從上至下布局全部生效,就能計算高度烂瘫,不要多次調(diào)用媒熊;
- 由于UITableView繪制過程中多次調(diào)用繪制,所以緩存高度計算結(jié)果坟比,可以有效的增加滑動流暢度芦鳍;
- 當(dāng)cell高度改變,記得及時替換緩存葛账;
三.離屏渲染
觸發(fā)條件:CALayer 的 border柠衅、圓角、陰影籍琳、遮罩(mask)菲宴,CASharpLayer 的矢量圖形顯示魂贬。
主要問題:GPU占滿,CPU空閑
解決方法:
1.開啟CALayer.shouldRasterize 裙顽,轉(zhuǎn)嫁到CPU上;
2.粗暴畫圖/截圖實現(xiàn)border和圓角宣谈;
3.砍死設(shè)計師愈犹。
最近拖延癥有點厲害,這篇文章想寫了很久都沒寫出來闻丑,我先摘要一部分思路出來漩怎。
曾經(jīng)的需求是這樣的:
初期方案是圖片下載完成后,裁成圓形嗦嗡,然后外面用貝塞爾畫一個圈勋锤,根據(jù)不同的UI緩存不同多個帶圈兒的圖;
然而...神奇的產(chǎn)品第二版給我整出了無數(shù)個各種不同大小侥祭、間距叁执、透明度的圈。這樣就意味著我得緩存無數(shù)帶著各色圈兒的圖矮冬。
后來突然靈光一閃谈宛,單獨設(shè)layer的圓角貌似不引發(fā)離屏渲染(有說法是iOS8之后)。所以后來方案變成胎署,UIView嵌套一個UIImageView吆录,只緩存裁剪過的圖片。
但是這樣的話琼牧,其實UIKit的創(chuàng)建要比讀取畫好的圖更耗性能恢筝。所以這是個終極的問題,時間/空間巨坊,究竟選哪個撬槽。
四.圖片的處理
1.SDWebImage的外層干了啥
這篇文章寫了1/4的樣子,詳細(xì)的會在這篇文章里抱究,十分心疼自己恢氯。下面簡單說說SDWebImage的外層都干了啥:
- 重用cell的時候,cancel之前下載operation鼓寺;
- 二級緩存:memory/disk勋拟;
- 合并回調(diào),多次調(diào)用只回調(diào)一次妈候;
2.項目中實際遇到的問題
我們做了個類似SDWebImage的東西敢靡,來實現(xiàn)神奇的需求,過程中遇到了一些問題:
-
從disk讀取需要時間苦银,在memory沒有的情況下啸胧,導(dǎo)致image = nil的一瞬間閃動:我把SDWeb的demo改了改赶站,左邊按鈕ReloadData,右邊按鈕清除內(nèi)存(memory)緩存纺念,在reload的一瞬間贝椿,展示了placeHolder的圖。
UICollectionView, reloadData 迷之移位閃爍問題:當(dāng)reloadData的時候陷谱,重用的cell神奇的變換了次序烙博,此時memory里只要一圖片不存在,就會有一瞬間的閃動烟逊。
-
上面兩個問題的解決方法有:
- 盡量避免reloadData渣窜,可以找到visibleCells,一一更換換圖片宪躯;
- 優(yōu)化細(xì)節(jié)乔宿,在發(fā)現(xiàn)disk里面有圖的時候,image不設(shè)為nil访雪,避免閃動详瑞;
- 保證memory緩存的大小和數(shù)量,能滿足界面需求冬阳;
請求沒有合并/回調(diào)沒有合并蛤虐;
這個比較好解決,就是請求之前判斷該請求是否在執(zhí)行肝陪,若在執(zhí)行驳庭,則將回調(diào)暫存到該請求下,等完成后一并調(diào)用氯窍。
五.ScrollView滑動優(yōu)化
1.滑動時的代理
- scrollViewDidScroll:實際上就是contentOffset的KVO
- scrollViewWillBeginDragging:
- scrollViewWillEndDragging: withVelocity: (points/millisecond這實際上是個速度的參數(shù))targetContentOffset:(這是一個可以傳值的指針饲常,可以控制最后的減速動畫)
- scrollViewDidEndDragging: willDecelerate:(拖動結(jié)束,如果仍有速度狼讨,會執(zhí)行后面兩個方法)
- scrollViewWillBeginDecelerating:
- scrollViewDidEndDecelerating:
- 需要注意scrollView的dragging屬性在decelerate的過程中仍然為YES
2.特殊情況
drag完贝淤,正decelerating時(didEndDecelerating尚未調(diào)用),強(qiáng)行再次drag(單指停止滑動政供,雙指連環(huán)滑)
- 單指停止滑動:沒有decelerate 播聪,willBeginDecelerating不會被調(diào)用,但前一次留下的 didEndDecelerating 會被調(diào)用(后面會結(jié)合VVWeibo的例子講述這里怎么處理)
- 雙指連環(huán)滑動:willBeginDecelerating會先于didEndDecelerating調(diào)用布隔,就是說這種情況didEndDecelerating會在你手指離開屏幕且屏幕停止的情況下調(diào)用离陶。
3.VVWeibo的做法
- scrollViewWillEndDragging: withVelocity: targetContentOffset:時,可以從targetContentOffset判斷即將加載的那一頁cell衅檀,從而預(yù)先加載招刨,UITableView有傳入rect返回cells的方法,UICollectionView得強(qiáng)行取兩個點獲得這兩個點cell的IndexPath哀军,然后得到cells沉眶。
- 遇到前文單指停止的處理打却,VVWeibo是在UITableView的子類捕獲了touchEvent,然后reloadData谎倔,我就沒有做子類了柳击。最后做了一系列神奇的判斷,然后reload片习。但是仍然遇到了 reloadData 迷之移位閃爍問題腻暮;后來我加入了速度的判斷,這個已經(jīng)不會觸發(fā)了毯侦,我就暫時注銷掉了,等待下一步優(yōu)化具垫。
4.最迷的問題
UICollectionViewLayout的prepareLayout調(diào)用了過多次數(shù)侈离,是因為shouldInvalidateLayoutForBoundsChange:這個方法災(zāi)難的調(diào)用了多次,newBounds的x和y實際上隨著滑動一直在變筝蚕,return YES的話就一直重新布局卦碾,最后用magicNumber存他的size,當(dāng)size變化才返回YES起宽,就很強(qiáng)行的解決了洲胖。
六.魔鬼般的視頻播放
這里涉及業(yè)務(wù)邏輯過多,我也不方便多寫坯沪,就寫一些過程中遇到的問題:
scrollViewDidEndDecelerating的VisibleItems為nil绿映。換個線程/延時去取,就能取到腐晾。
縮小播放區(qū)域叉弦,跟前面的取點找目標(biāo)cell的操作類似,找出首尾點藻糖,中間的cell淹冰,即是需要播放的cell。
多個等待播放的AVPlayerItemVideoOutput巨柒,會導(dǎo)致一部分失效樱拴,內(nèi)存越小的機(jī)子上越明顯。
解決方法:播放前再把url賦給playerItem洋满,一定程度避免過多playerItem失敗的問題晶乔;出入頁面找到所有playerItem并干掉,避免影響其他播放芦岂;
GLKView的reuse在狂滑的時候十分耗內(nèi)存瘪弓,而不reuse的話,重用的時候禽最,會顯示上一個頁面的殘影腺怯,解決方法是先用圖片蓋住殘影袱饭,在播前,清理上一次播放的殘余呛占;
加入一個Timer虑乖,通過記錄偏移量來控制滑動速度,高速度的時候晾虑,不繪制/下載圖片疹味。這樣也解決了雙指狂滑的時候,無法很好的判斷當(dāng)前是否繪制的問題帜篇。
Conference
http://wereadteam.github.io/2016/05/03/WeRead-Performance/ 微信讀書 iOS 性能優(yōu)化總結(jié)
http://blog.ibireme.com/2015/11/12/smooth_user_interfaces_for_ios/ iOS 保持界面流暢的技巧
http://blog.sunnyxx.com/2015/05/17/cell-height-calculation/ 優(yōu)化UITableViewCell高度計算的那些事
http://tech.glowing.com/cn/practice-in-uiscrollview/ UIScrollView 實踐經(jīng)驗
《High Performance iOS Apps》這本真是神書糙捺,有興趣深入學(xué)習(xí)優(yōu)化的可以去看看,中文的貌似有美團(tuán)技術(shù)團(tuán)隊翻譯的
簡書已經(jīng)棄用笙隙,歡迎移步我的小專欄:
https://xiaozhuanlan.com/dahuihuiiOS