UITableView 滾動流程性優化

影響 UITableView 滾動的流暢性的原因

1、 在代理方法中做了過多的計算佔用了 UI 線程的時間
2、同上
3、Cell 中 view 的組織複雜

關於第一點,首先要明白 tableview 的代理(這裏指 datasource 和 delegate 的那套方法,下同)方法的調用順序,和時機。對於一般的應用會有如下順序:

1、向代理要 number Of Rows。
2、對於每行向代理要 height For Row At Index Path。
3、向代理要 當前屏幕可見的 cell For Row At Index Path 。(實測顯示4寸屏的手機會取 屏幕顯示數量+2,3.5寸屏同4寸屏數量,雖然3.5寸屏可顯示的cell 數量要小於 4寸屏!)
4、然後 cell 就顯示出來了。

tableView:heightForRowAtIndexPath:

很多人都把優化的重點放到了 cell for row at indexpath 那個方法裏了,在這裏儘可能的少計算,但是卻忽略了另一個很輕鬆就能提升加載時間的方法 :

   - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath

Table View 在每次 reload data 時都會要所有 cell 的高度!這就是說你有一百行 cell,就像代理要100次每個cell 的高度,而不是當前屏幕顯示的cell 的數量的高度!雖然在 iOS 7 下多了計算 cell 高度的方法,但是減少 計算高度時的時間,對於提升加載 Table View 的速度有非常明顯的提高!

- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath
(**iOS 7專用**)

但是有人說了,我早聽別人說了,reloadData 方法儘量不要調用,我插入新行都用 insertRowsAtIndexPaths:withRowAnimation: 刪除也用 delete 那個,這個總行了吧?!

這樣也不能忽略 height For Row At Index Path 這個回調的重要性。因爲在每次插入或者刪除一行後同樣需要調用一遍 所有行 的這個回調方法!是所有行!你沒看錯,所有隻是簡單的減少一個代理方法的計算量,就可以明顯的提升加載速度。

對於提升 tableView:heightForRowAtIndexPath: 計算量,就是儘可能的讓這個方法的計算複雜度爲 O(1),就是隻是簡單的從數組中取一個值,然後返回!

也許有人又要問了,我的應用都是動態的高度,就像微博那樣的,不定數量的文字,可能還有圖片,大小也不固定,這些怎麼返回固定的高度啊?

我指的固定高度不是 row 的高度都一樣那種固定,而是讓在 tableView:heightForRowAtIndexPath: 這個回調裏取這個高度的時間是近乎固定的。

對於高度的計算,還有個小細節需要注意,就是如果 row 的高度都一定,那就刪除代理中的這個 tableView:heightForRowAtIndexPath: 方法,設置 Table View 的 rowHeight 屬性,相似的 numberOfRowsInSection: 系列的方法,我就不都寫出來了。蘋果的文檔裏介紹這樣也可以減少了調用時間。

現在迴歸正題。對於 cell 高度不固定的,傳統的方法是爲 cell 寫個計算行高的類方法,傳入那些動態的元素(文字,圖片等),然後返回計算後的高度。在 tableView:heightForRowAtIndexPath: 中調用這個方法,填入需要的參數計算cell 高度。這當然沒有什麼問題,只是要是計算量很複雜,你每次 reloadData ,光計算行高就要花去 rowCount * 單行高評價計算時間,想想有100行,你不定期的需要 reloadData 或者 insert(delete) row。。。。解決辦法就是:

用 “空間換時間”

將計算行高的時間提前到從服務器摟回數據的時候,計算完了高度一併寫回數據庫,別告訴我你在主線程裏阻塞式的處理網絡請求。。。。面壁思過去吧,別浪費了 GCD,NSOperationQueue的青春。最先想到的還是 NSThread 的同學,證明你已經老了。。。現在幾乎大部分的多線程操作都不需要用到 NSThread 和 runloop了。

tableView:cellForRowAtIndexPath:

說完了計算 cell 行高的優化,現在來談 tableView:cellForRowAtIndexPath: 回調的優化。優化思路同上,也是通過預處理減少在這個回調中的計算時間。這個回調重點談的是對圖片異步加載的優化。

圖片異步加載無非就是在這個方法裏發起異步請求,圖片加載完後根據 UIImageView 的引用設置圖片。有經驗的程序員可能會使用 懶加載 的方式減少快速滑動時因爲網絡請求過於頻繁與切換線程顯示圖片造成的卡頓。這裏還有個問題,拿回來的圖片一定和最後顯示的大小不一樣,有時候偷懶,直接設置 image view 的 contentMode 屬性要 image view 自己 壓縮。這是一個很取巧的方法,但是對 table view 的滾動速度也會造成 不容忽視 的影響。對圖片變形需要對圖片做 transform ,每次壓縮圖片都要對圖片乘以一個變換矩陣,如果你的圖片很多,這個計算量是不同忽視的。

優化建議:從網絡摟回來圖片後先根據需要顯示的圖片大小切成合適大小的圖,每次只顯示處理過大小的圖片,當查看大圖時在顯示大圖。如果服務器能直接返回預處理好的小圖和圖片的大小更好。

使用 Instrument 的 Core Animation 模板可以查看圖片的壓縮情況。如圖:

Core Animation 模板

Instrument 中的 Core Animation 模板只有在調試真機時纔有,調試模擬器上的應用沒有這個模板!!!但是可以在模擬器的 Debug 菜單下找到這些調試選項。

切記:調試應用性能一定要用真機,Mac 的性能完爆 iPhone,所有不要說我的應用在模擬器上調試時不卡啊!模擬器只能模擬 iOS 軟件的運行環境,不能模擬硬件性能!

Color Misaligned Images

這些選項對設備的所有應用有效,也就是說你不需要選擇 target 就能調試 (方便競品分析 :)!

對於 Misaligned images 會有兩種顏色:一種是洋紅色,另一種是黃色。

Misaligned images

洋紅色是因爲像素沒對齊,比如上面的 label,一般情況下因爲像素沒對齊,需要抗鋸齒,圖像會出現模糊的現象。

解決辦法:在設置 view 的 frame 時,在高分屏避免出現 21.3,6.7這樣的小數,尤其是 x,y座標,用 ceil 或 floor 或 round 取整。每 0.5 個點對應一個 pixel,0.3,0.7這樣的就難爲 iPhone 了,低分屏不要出現小數。

黃色是因爲顯示的圖片實際大小與顯示大小不同,對圖片進行了拉伸,測試顯示使用 image view 顯示實際大小的圖也會變黃。

減少洋紅色和黃色可以提升滾動的流暢性

手動 Drawing 視圖提升流暢性

如果通過上面的方法,滾動速度還不能達到可以容忍的速度,那就只剩下最後一個辦法了,手動繪製視圖。

手動繪製方法,不是直接子類化 UITableViewCell,然後覆蓋 drawRect: 方法,這樣你會得到一個大黑塊!因爲 cell 中不是隻有一個 content view。如果不瞭解 cell 的層次結構,可以用 Reveal 去看下。

繪製 cell 不建議使用 UIView,建議使用 CALayer。 UIView 的繪製是建立在 CoreGraphic 上的,使用的是 CPU。CALayer 使用的是 Core Animation,CPU,GPU 通吃,由系統決定使用哪個。View的繪製使用的是自下向上的一層一層的繪製,然後渲染。Layer處理的是 Texure,利用 GPU 的 Texture Cache 和獨立的浮點數計算單元加速 紋理 的處理。

GPU 不喜歡 透明,所以所有的繪圖一定要弄成不透明,對於圓角和陰影這些的可以截個僞透明的小圖然後繪製上去。在layer的回調裏一定也只做繪圖,不做計算!

發佈了2 篇原創文章 · 獲贊 11 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章