iOS界面流暢技巧之微博 Demo 性能優化技巧

微博 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。這個方法比較有技巧性,而且對於滑動性能來講提高也很大,惟一的缺點就是快速滑動中會出現大量空白內容。若是你不想實現比較麻煩的異步繪製但又想保證滑動的流暢性,這個技巧是個不錯的選擇。oop

全局併發控制

當我用 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 消耗等,在定位界面卡頓的問題時,這是最好的工具。

相關文章
相關標籤/搜索