
微博 Demo 性能優化技巧
我為了演示 YYKit 的功能,實作了微博和 Twitter 的 Demo,并為它們做了不少性能優化,下面就是優化時用到的一些技巧,
預排版
當獲取到 API JSON 資料后,我會把每條 Cell 需要的資料都在后臺執行緒計算并封裝為一個布局物件 CellLayout,CellLayout 包含所有文本的 CoreText 排版結果、Cell 內部每個控制元件的高度、Cell 的整體高度,每個 CellLayout 的記憶體占用并不多,所以當生成后,可以全部快取到記憶體,以供稍后使用,這樣,TableView 在請求各個高度函式時,不會消耗任何多余計算量;當把 CellLayout 設定到 Cell 內部時,Cell 內部也不用再計算布局了,
對于通常的 TableView 來說,提前在后臺計算好布局結果是非常重要的一個性能優化點,為了達到最高性能,你可能需要犧牲一些開發速度,不要用 Autolayout 等技術,少用 UILabel 等文本控制元件,但如果你對性能的要求并不那么高,可以嘗試用 TableView 的預估高度的功能,并把每個 Cell 高度快取下來,這里有個來自百度知道團隊的開源專案可以很方便的幫你實作這一點:FDTemplateLayoutCell,
預渲染
微博的頭像在某次改版中換成了圓形,所以我也跟進了一下,當頭像下載下來后,我會在后臺執行緒將頭像預先渲染為圓形并單獨保存到一個 ImageCache 中去,
一個開發者,有一個學習的氛圍跟一個交流圈子特別重要,這是一個我的iOS交流群:1012951431, 分享BAT,阿里面試題、面試經驗,討論技術, 大家一起交流學習成長!希望幫助開發者少走彎路,
對于 TableView 來說,Cell 內容的離屏渲染會帶來較大的 GPU 消耗,在 Twitter Demo 中,我為了圖省事兒用到了不少 layer 的圓角屬性,你可以在低性能的設備(比如 iPad 3)上快速滑動一下這個串列,能感受到雖然串列并沒有較大的卡頓,但是整體的平均幀數降了下來,用 Instument 查看時能夠看到 GPU 已經滿負荷運轉,而 CPU 卻比較清閑,為了避免離屏渲染,你應當盡量避免使用 layer 的 border、corner、shadow、mask 等技術,而盡量在后臺執行緒預先繪制好對應內容,
異步繪制
我只在顯示文本的控制元件上用到了異步繪制的功能,但效果很不錯,我參考 ASDK 的原理,實作了一個簡單的異步繪制控制元件,這塊代碼我單獨提取出來,放到了這里:YYAsyncLayer,YYAsyncLayer 是 CALayer 的子類,當它需要顯示內容(比如呼叫了 [layer setNeedDisplay])時,它會向 delegate,也就是 UIView 請求一個異步繪制的任務,在異步繪制時,Layer 會傳遞一個 BOOL(^isCancelled)()這樣的 block,繪制代碼可以隨時呼叫該 block 判斷繪制任務是否已經被取消,
當 TableView 快速滑動時,會有大量異步繪制任務提交到后臺執行緒去執行,但是有時滑動速度過快時,繪制任務還沒有完成就可能已經被取消了,如果這時仍然繼續繪制,就會造成大量的 CPU 資源浪費,甚至阻塞執行緒并造成后續的繪制任務遲遲無法完成,我的做法是盡量快速、提前判斷當前繪制任務是否已經被取消;在繪制每一行文本前,我都會呼叫 isCancelled() 來進行判斷,保證被取消的任務能及時退出,不至于影響后續操作,
目前有些第三方微博客戶端(比如 VVebo、墨客等),使用了一種方式來避免高速滑動時 Cell 的繪制程序,相關實作見這個專案:VVeboTableViewDemo,它的原理是,當滑動時,松開手指后,立刻計算出滑動停止時 Cell 的位置,并預先繪制那個位置附近的幾個 Cell,而忽略當前滑動中的 Cell,這個方法比較有技巧性,并且對于滑動性能來說提升也很大,唯一的缺點就是快速滑動中會出現大量空白內容,如果你不想實作比較麻煩的異步繪制但又想保證滑動的流暢性,這個技巧是個不錯的選擇,
全域并發控制
當我用 concurrent queue 來執行大量繪制任務時,偶爾會遇到這種問題:

大量的任務提交到后臺佇列時,某些任務會因為某些原因(此處是 CGFont 鎖)被鎖住導致執行緒休眠,或者被阻塞,concurrent queue 隨后會創建新的執行緒來執行其他任務,當這種情況變多時,或者 App 中使用了大量 concurrent queue 來執行較多任務時,App 在同一時刻就會存在幾十個執行緒同時運行、創建、銷毀,CPU 是用時間片輪轉來實作執行緒并發的,盡管 concurrent queue 能控制執行緒的優先級,但當大量執行緒同時創建運行銷毀時,這些操作仍然會擠占掉主執行緒的 CPU 資源,ASDK 有個 Feed 串列的 Demo:SocialAppLayout,當串列內 Cell 過多,并且非常快速的滑動時,界面仍然會出現少量卡頓,我謹慎的猜測可能與這個問題有關,
使用 concurrent queue 時不可避免會遇到這種問題,但使用 serial queue 又不能充分利用多核 CPU 的資源,我寫了一個簡單的工具 YYDispatchQueuePool,為不同優先級創建和 CPU 數量相同的 serial queue,每次從 pool 中獲取 queue 時,會輪詢回傳其中一個 queue,我把 App 內所有異步操作,包括影像解碼、物件釋放、異步繪制等,都按優先級不同放入了全域的 serial queue 中執行,這樣盡量避免了過多執行緒導致的性能問題,
更高效的異步圖片加載
SDWebImage 在這個 Demo 里仍然會產生少量性能問題,并且有些地方不能滿足我的需求,所以我自己實作了一個性能更高的圖片加載庫,在顯示簡單的單張圖片時,利用 UIView.layer.contents 就足夠了,沒必要使用 UIImageView 帶來額外的資源消耗,為此我在 CALayer 上添加了 setImageWithURL 等方法,除此之外,我還把圖片解碼等操作通過 YYDispatchQueuePool 進行管理,控制了 App 總執行緒數量,
其他可以改進的地方
上面這些優化做完后,微博 Demo 已經非常流暢了,但在我的設想中,仍然有一些進一步優化的技巧,但限于時間和精力我并沒有實作,下面簡單列一下:
串列中有不少視覺元素并不需要觸摸事件,這些元素可以用 ASDK 的圖層合成技術預先繪制為一張圖,
再進一步減少每個 Cell 內圖層的數量,用 CALayer 替換掉 UIView,
目前每個 Cell 的型別都是相同的,但顯示的內容卻各部一樣,比如有的 Cell 有圖片,有的 Cell 里是卡片,把 Cell 按型別劃分,進一步減少 Cell 內不必要的視圖物件和操作,應該能有一些效果,
把需要放到主執行緒執行的任務劃分為足夠小的塊,并通過 Runloop 來進行調度,在每個 Loop 里判斷下一次 VSync 的時間,并在下次 VSync 到來前,把當前未執行完的任務延遲到下一個機會去,這個只是我的一個設想,并不一定能實作或起作用,
如何評測界面的流暢度
最后還是要提一下,“過早的優化是萬惡之源”,在需求未定,性能問題不明顯時,沒必要嘗試做優化,而要盡量正確的實作功能,做性能優化時,也最好是走修改代碼 -> Profile -> 修改代碼這樣一個流程,優先解決最值得優化的地方,
如果你需要一個明確的 FPS 指示器,可以嘗試一下 KMCGeigerCounter,對于 CPU 的卡頓,它可以通過內置的 CADisplayLink 檢測出來;對于 GPU 帶來的卡頓,它用了一個 1x1 的 SKView 來進行監視,這個專案有兩個小問題:SKView 雖然能監視到 GPU 的卡頓,但引入 SKView 本身就會對 CPU/GPU 帶來額外的一點的資源消耗;這個專案在 iOS 9 下有一些兼容問題,需要稍作調整,
我自己也寫了個簡單的 FPS 指示器:FPSLabel 只有幾十行代碼,僅用到了 CADisplayLink 來監視 CPU 的卡頓問題,雖然不如上面這個工具完善,但日常使用沒有太大問題,
最后,用 Instuments 的 GPU Driver 預設,能夠實時查看到 CPU 和 GPU 的資源消耗,在這個預設內,你能查看到幾乎所有與顯示有關的資料,比如 Texture 數量、CA 提交的頻率、GPU 消耗等,在定位界面卡頓的問題時,這是最好的工具,
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/7490.html
標籤:iOS
下一篇:iOS核心影片高級技巧-1
