微博 Demo 性能優(yōu)化技巧

2018-02-24 15:29 更新

微博 Demo 性能優(yōu)化技巧

我為了演示 YYKit 的功能,實現(xiàn)了微博和 Twitter 的 Demo,并為它們做了不少性能優(yōu)化,下面就是優(yōu)化時用到的一些技巧。

預排版

當獲取到 API JSON 數(shù)據(jù)后,我會把每條 Cell 需要的數(shù)據(jù)都在后臺線程計算并封裝為一個布局對象 CellLayout。CellLayout 包含所有文本的 CoreText 排版結果、Cell 內部每個控件的高度、Cell 的整體高度。每個 CellLayout 的內存占用并不多,所以當生成后,可以全部緩存到內存,以供稍后使用。這樣,TableView 在請求各個高度函數(shù)時,不會消耗任何多余計算量;當把 CellLayout 設置到 Cell 內部時,Cell 內部也不用再計算布局了。

對于通常的 TableView 來說,提前在后臺計算好布局結果是非常重要的一個性能優(yōu)化點。為了達到最高性能,你可能需要犧牲一些開發(fā)速度,不要用 Autolayout 等技術,少用 UILabel 等文本控件。但如果你對性能的要求并不那么高,可以嘗試用 TableView 的預估高度的功能,并把每個 Cell 高度緩存下來。這里有個來自百度知道團隊的開源項目可以很方便的幫你實現(xiàn)這一點:FDTemplateLayoutCell

預渲染

微博的頭像在某次改版中換成了圓形,所以我也跟進了一下。當頭像下載下來后,我會在后臺線程將頭像預先渲染為圓形并單獨保存到一個 ImageCache 中去。

對于 TableView 來說,Cell 內容的離屏渲染會帶來較大的 GPU 消耗。在 Twitter Demo 中,我為了圖省事兒用到了不少 layer 的圓角屬性,你可以在低性能的設備(比如 iPad 3)上快速滑動一下這個列表,能感受到雖然列表并沒有較大的卡頓,但是整體的平均幀數(shù)降了下來。用 Instument 查看時能夠看到 GPU 已經(jīng)滿負荷運轉,而 CPU 卻比較清閑。為了避免離屏渲染,你應當盡量避免使用 layer 的 border、corner、shadow、mask 等技術,而盡量在后臺線程預先繪制好對應內容。

異步繪制

我只在顯示文本的控件上用到了異步繪制的功能,但效果很不錯。我參考 ASDK 的原理,實現(xiàn)了一個簡單的異步繪制控件。這塊代碼我單獨提取出來,放到了這里:YYAsyncLayer。YYAsyncLayer 是 CALayer 的子類,當它需要顯示內容(比如調用了 [layer setNeedDisplay])時,它會向 delegate,也就是 UIView 請求一個異步繪制的任務。在異步繪制時,Layer 會傳遞一個?BOOL(^isCancelled)()這樣的 block,繪制代碼可以隨時調用該 block 判斷繪制任務是否已經(jīng)被取消。

當 TableView 快速滑動時,會有大量異步繪制任務提交到后臺線程去執(zhí)行。但是有時滑動速度過快時,繪制任務還沒有完成就可能已經(jīng)被取消了。如果這時仍然繼續(xù)繪制,就會造成大量的 CPU 資源浪費,甚至阻塞線程并造成后續(xù)的繪制任務遲遲無法完成。我的做法是盡量快速、提前判斷當前繪制任務是否已經(jīng)被取消;在繪制每一行文本前,我都會調用 isCancelled() 來進行判斷,保證被取消的任務能及時退出,不至于影響后續(xù)操作。

目前有些第三方微博客戶端(比如 VVebo、墨客等),使用了一種方式來避免高速滑動時 Cell 的繪制過程,相關實現(xiàn)見這個項目:VVeboTableViewDemo。它的原理是,當滑動時,松開手指后,立刻計算出滑動停止時 Cell 的位置,并預先繪制那個位置附近的幾個 Cell,而忽略當前滑動中的 Cell。這個方法比較有技巧性,并且對于滑動性能來說提升也很大,唯一的缺點就是快速滑動中會出現(xiàn)大量空白內容。如果你不想實現(xiàn)比較麻煩的異步繪制但又想保證滑動的流暢性,這個技巧是個不錯的選擇。

全局并發(fā)控制

當我用 concurrent queue 來執(zhí)行大量繪制任務時,偶爾會遇到這種問題:

大量的任務提交到后臺隊列時,某些任務會因為某些原因(此處是 CGFont 鎖)被鎖住導致線程休眠,或者被阻塞,concurrent queue 隨后會創(chuàng)建新的線程來執(zhí)行其他任務。當這種情況變多時,或者 App 中使用了大量 concurrent queue 來執(zhí)行較多任務時,App 在同一時刻就會存在幾十個線程同時運行、創(chuàng)建、銷毀。CPU 是用時間片輪轉來實現(xiàn)線程并發(fā)的,盡管 concurrent queue 能控制線程的優(yōu)先級,但當大量線程同時創(chuàng)建運行銷毀時,這些操作仍然會擠占掉主線程的 CPU 資源。ASDK 有個 Feed 列表的 Demo:SocialAppLayout,當列表內 Cell 過多,并且非??焖俚幕瑒訒r,界面仍然會出現(xiàn)少量卡頓,我謹慎的猜測可能與這個問題有關。

使用 concurrent queue 時不可避免會遇到這種問題,但使用 serial queue 又不能充分利用多核 CPU 的資源。我寫了一個簡單的工具?YYDispatchQueuePool,為不同優(yōu)先級創(chuàng)建和 CPU 數(shù)量相同的 serial queue,每次從 pool 中獲取 queue 時,會輪詢返回其中一個 queue。我把 App 內所有異步操作,包括圖像解碼、對象釋放、異步繪制等,都按優(yōu)先級不同放入了全局的 serial queue 中執(zhí)行,這樣盡量避免了過多線程導致的性能問題。

更高效的異步圖片加載

SDWebImage 在這個 Demo 里仍然會產(chǎn)生少量性能問題,并且有些地方不能滿足我的需求,所以我自己實現(xiàn)了一個性能更高的圖片加載庫。在顯示簡單的單張圖片時,利用 UIView.layer.contents 就足夠了,沒必要使用 UIImageView 帶來額外的資源消耗,為此我在 CALayer 上添加了 setImageWithURL 等方法。除此之外,我還把圖片解碼等操作通過 YYDispatchQueuePool 進行管理,控制了 App 總線程數(shù)量。

其他可以改進的地方

上面這些優(yōu)化做完后,微博 Demo 已經(jīng)非常流暢了,但在我的設想中,仍然有一些進一步優(yōu)化的技巧,但限于時間和精力我并沒有實現(xiàn),下面簡單列一下:

列表中有不少視覺元素并不需要觸摸事件,這些元素可以用 ASDK 的圖層合成技術預先繪制為一張圖。

再進一步減少每個 Cell 內圖層的數(shù)量,用 CALayer 替換掉 UIView。

目前每個 Cell 的類型都是相同的,但顯示的內容卻各部一樣,比如有的 Cell 有圖片,有的 Cell 里是卡片。把 Cell 按類型劃分,進一步減少 Cell 內不必要的視圖對象和操作,應該能有一些效果。

把需要放到主線程執(zhí)行的任務劃分為足夠小的塊,并通過 Runloop 來進行調度,在每個 Loop 里判斷下一次 VSync 的時間,并在下次 VSync 到來前,把當前未執(zhí)行完的任務延遲到下一個機會去。這個只是我的一個設想,并不一定能實現(xiàn)或起作用。

以上內容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號