讀后感系列之TableView滾動性能優(yōu)化

本文為純手打,內(nèi)容取自南峰子老師的博客:http://southpeak.github.io/

里面有些地方?jīng)]有實踐,也不是很理解,大家共同學(xué)習(xí)。



內(nèi)建方法:

首先是重用cell/header/footer的單個實例,幾遍是我們需要顯示多個.這是優(yōu)化UIScrollerView最明顯的方式,為了正確的使用它,你應(yīng)該只有cell/header/footer類,一次性初始化他們,并返回給UITableView.

蘋果開發(fā)文檔里有重用cell的流程.

重要的事情是:在UITableView的dataSource中實現(xiàn)的tableView:cellForRowAtIndexPath:方法,需要為每個cell調(diào)用一次,他應(yīng)該快速執(zhí)行.所以需要盡可能快地返回重用cell實例.不要在這里進(jìn)行數(shù)據(jù)綁定,因為目前在屏幕上還沒有cell.為了執(zhí)行數(shù)據(jù)綁定可以在UITableView的delegate方法tableView:willDisplayCell:forRowAtIndexPath:中進(jìn)行.這個方法在顯示之前會被調(diào)用.

這個方法對于cell定高的UITableView來說沒有意義,但如果由于某些原因需要動態(tài)高度的cell的話,這個方法可以很容易地讓滑動更流暢.

正如我們所知,UITableView是UIScrollerView的子類,而UIScrollerView的作用是讓用戶可以與比屏幕尺寸更大的區(qū)域交互.任何UIScrollerView的實例都是用諸如contentSizew、contentOffset和其他許多屬性來將正確的區(qū)域顯示給用戶.

但是UITableView得問他在哪?正如所解釋的一樣,UITableView不會同時維護(hù)所有cell的實例。相反他只需要維護(hù)顯示給用戶的那些cell。

那么,UITableView是如何知道她的contentSize呢?他是通過計算所有cell的高度之和來計算contentSize的值。

UITableView的delegate方法tableView:heightForRowAtIndexPath:會為每個cell調(diào)用一次,所以你應(yīng)該非??斓胤祷馗叨戎怠?/p>

很多人會犯一個錯誤,他們會在布局初始化cell實例并綁定數(shù)據(jù)后獲取他們的高度。如果你想優(yōu)化滑動的性能,就不應(yīng)該以這種方式來計算cell的高度,因為這事難以置信的低效,iOS設(shè)備標(biāo)準(zhǔn)的60FPS將會降低到15-20FPS,滑動會變得很慢。

如果我們沒有一個cell的實例,那如何計算他的高度呢?

它使用類方法,并基于傳入的寬度及顯示的數(shù)據(jù)來計算高度值

可以用以下方式來使用上面這個方法返回高度值給UITableView:

從iOS8 開始,我們可以在UITableView的delegate中使用自動高度計算,而不需要實現(xiàn)上面提到的方法。為了實現(xiàn)這一功能,你可能會使用AutoLayout,并將rowHeight變量設(shè)置為UITableViewAutomaticDimension??梢栽?a target="_blank" rel="nofollow">StackOverflow中找到更多詳細(xì)信息。

如果你想讓你的App在所有設(shè)備上都能平滑的滾動,你會發(fā)現(xiàn)這種方法難以置信的慢,你使用的子視圖越多,AutoLayout的效率越低,原因是隱藏在底層的命名為“Cassowary”的約束求解系統(tǒng)。如果布局中子視圖越多,那么需要求解的約束也越多,進(jìn)而返回cell給UITableView所花的時間也越多。

使用內(nèi)建方法優(yōu)化UITableView的正確方法是:

重用cell實例:對于特殊類型的cell,你應(yīng)該只有一個實例,而沒有更多。

不要在cellForRowAtIndexPath:方法中綁定數(shù)據(jù),因為在此時cell還沒有顯示。可以使用UITableView的delegate中的tableView:willDisplayCell:forRowAtIndexPath:方法。

快速計算cell高度。


更深一步


上面提到的這些點不足以實現(xiàn)真正的平滑滾動,特別是當(dāng)你需要實現(xiàn)一些復(fù)雜的cell(如有大量的簡便、視圖、交互元素、一些修飾元素等等)時,這變得尤其明顯。

這種情況下,UITableView很容易變得緩慢,即便是做了上面所有的事情。UITableViewCell中的視圖越多,滑動時FPS越低。但在使用了手動布局優(yōu)化了高度計算后,問題就不在布局了,而在渲染了。

讓我們把關(guān)注點放在UIView的opaque上屬性上,文檔中說它用于輔助繪圖系統(tǒng)定義UIView是否透明,如果不透明,則繪圖系統(tǒng)在渲染視圖時可以做一些優(yōu)化,以提高性能。

我們需要性能,或者不是?用戶可能快速地滑動table,如使用scrollsToTop特性,但他們可能沒有最新的iPhone,所以cell必須快速的被渲染。比通常的視圖更快。

渲染最慢的操作之一是混合(blending)?;旌喜僮饔蒅PU來執(zhí)行,因為這個硬件就是用來做混合操作的(當(dāng)然不只是混合)。

你可能已經(jīng)猜到提高性能的方法是減少混合操作的次數(shù),在此之前,我們需要找到它。

在模擬器上運(yùn)行APP,在模擬器的菜單中選擇‘Debug’,然后選中‘Color Blended Layers’。然后模擬器就會將全部區(qū)域顯示為兩種顏色:綠色和紅色。(綠色沒有混合,紅色區(qū)域表示有混合操作)每種情況都應(yīng)該仔細(xì)研究,不同的情況需要使用不同的方法來避免混合。一般做法是設(shè)置backgroundColor來實現(xiàn)非透明。

但有時候可能更復(fù)雜。有一個漸變,但是沒有混合。


如果想要使用CAGradientLayer來實現(xiàn)這個效果,你將會很失望:在iPhone6中FPS將會降到25-30,快色滑動變得不可能。

這確實發(fā)生了,因為我們混合了兩個不同層的內(nèi)容:UILabel的CATextLayer和我們的CAGradientLayer。

當(dāng)設(shè)備需要執(zhí)行很多混合操作時,問題就出現(xiàn)了:GPU是滿載的,但CPU卻保持低負(fù)載,而顯得沒有太大用處。

所以需要怎么做呢?事實上,解決的方案是:使用CPU來渲染!這將不會加載GPU,這樣就無法執(zhí)行混合操作,例如,在執(zhí)行動畫的CALayer上。

我們可以在UIView的drawRect:方法中使用CoreGraphics操作來執(zhí)行CPU渲染,


通過這種方式,你會撤銷在一些UIView上(在任何情況下,他們都是不必要的)的所有緩存優(yōu)化操作。但是,這種方法禁用了一些混合操作,卸載GPU,從而使UITableView更順暢。

但是記?。哼@提高了渲染性能,不是因為CPU比GPU更快,他可以讓我們通過為讓CPU來執(zhí)行某些渲染任務(wù),從而卸載GPU,因為在很多情況下,CPU可能不是100%負(fù)載的。優(yōu)化混合操作的關(guān)鍵點是在平衡CPU和GPU的負(fù)載。(這個CoreGraphics好難理解感覺!)

優(yōu)化UITableView中繪制數(shù)據(jù)操作的小結(jié):

減少iOS執(zhí)行無用混合的區(qū)域:不要使用透明背景,使用模擬器或者Instruments來確認(rèn)這一點,如果可以盡量使用沒有混合的漸變。

優(yōu)化代碼,以平衡CPU和GPU的負(fù)載。你需要清楚的知道那部分渲染需要使用GPU,那部分可以使用CPU,以此保持平衡。

為特殊的cell類型編寫特殊的代碼。


像素獲取


自從有了Retina屏后,在Cocoa Touch環(huán)境下,我們就可以用屏幕點來取代像素了,同時屏幕點可以是浮點值。

現(xiàn)實生活中它可能是浮點值,例如,線段可能起始于x為0.25的地方。這時候,iOS將執(zhí)行子像素渲染。這一技術(shù)在應(yīng)用于特定類型的內(nèi)容是很有意義。但當(dāng)我們繪制平滑直線時則沒有必要。

如果所有的平滑線段都使用子像素渲染技術(shù)來渲染,那會讓你iOS執(zhí)行一些不必要的任務(wù),從而降低FPS。

什么情況下會出現(xiàn)這種不必要的子像素抗鋸齒操作呢?最常發(fā)生的情況是通過代碼計算而變成浮點值的視圖坐標(biāo),或者是一些不正常的圖片資源,這些圖片的大小不是對起到屏幕的物理像素上的(例如,你有一張在Retina顯示屏上的大小為60*61的圖片,而不是60*60的)。

在前面我們講到,要解決問題,首先需要找到問題在哪。在模擬器上運(yùn)行程序,在“Debug”菜單中選中“Color Misaligned Image”。

這一次有兩種高亮區(qū)域:品紅色區(qū)域會執(zhí)行子像素渲染,而黃色區(qū)域是圖片大小沒有對齊的情況。

通常,為了解決這個問題,你只要簡單地使用ceilf,floorf和CGRectIntegral方法來對坐標(biāo)做四舍五入處理。就是這樣!

通過上面的討論,我想建議你以下幾點:

對所有像素相關(guān)的數(shù)據(jù)做四舍五入處理,包括點坐標(biāo),UIView的高度和寬度。

跟蹤你的圖像資源:圖片必須是像素完美的,否則在Retina屏幕上渲染時,它會做不必要的抗鋸齒處理。

定期復(fù)查你的代碼,因為這種情況可能會經(jīng)常出現(xiàn)。


異步UI

每個中等以上規(guī)模的應(yīng)用都可能會使用帶有媒體內(nèi)容的cell:文本、圖片、動畫,甚至還有視頻。而所有這些都可能帶有裝飾元素:圓角頭像、帶‘#’號的文本、用戶名等。

我們已經(jīng)多次提及盡可能快地返回cell的需求,而在這里有一些麻煩:clipsToBounds很慢,圖片需要從網(wǎng)絡(luò)加載,需要在字符串中定位#號,和許多其他的問題。

優(yōu)化的目標(biāo)是很明確的:如果在主線程中執(zhí)行這些操作,則會讓你不能很快地返回cell。

在后臺加載圖片,在相同的地方處理圓角,然后將處理后的圖片制定給UIimageView。

立刻顯示文本,但在后臺定位#號,然后使用屬性字符串來刷新顯示。

在你的cell中,需要具體情況具體分析,但主要的思想是在后臺執(zhí)行大的操作。這可能不只是網(wǎng)絡(luò)代碼,你需要使用Instruments來找到他們。

記住:需要盡快返回cell

有時候,上面的所有技術(shù)可能都幫不上忙。如GPU仍然不能使用時(iPhone4+iOS7),cell中有很多內(nèi)容時,需要CALayer的支持以實現(xiàn)動畫時(在drawRect:中實現(xiàn)起來真的很麻煩)。

在這種情況下,我們需要在后臺渲染所有其他東西。此外他能在用戶快速滑動UITableView時有效提高FPS。

我們Facebook的應(yīng)用。為了檢測這些,你可能需要往下滑動足夠的高度,然后點擊狀態(tài)欄。列表會往上滑動,因此你可以清楚地看到此時沒有渲染cell,如果想要更精確,則不能及時獲得。

這很簡單,所以你可以自己試試,這時,你需要設(shè)置CALayer的drawsAsynchronously屬性為YES。

但是我們可以檢查這些行為的必要性,在模擬器上運(yùn)行程序,然后選擇“Debug”菜單中的“Color Offscreen-Rendered”?,F(xiàn)在所有在后臺渲染的區(qū)域都被高亮為黃色。


如果你為某些層開啟了這一模式,但是它沒有高亮顯示,那么它就不夠慢

為了在CALayer層找到瓶頸并進(jìn)一步減少它,你可以使用Instruments里面的Time Profiler。

(關(guān)于Instruments使用工具有Leaks、Allocations,Time Profiler幫助我們分析代碼的執(zhí)行實現(xiàn),找出導(dǎo)致程序變慢的原因,告訴我們‘實踐都去哪了’)

這里異步優(yōu)化UI的實現(xiàn)清單:

找到讓你的cell無法快速返回的瓶頸。

將操作移到后臺線程,并在主線程刷新顯示的內(nèi)容。

最后一招是設(shè)置你的CALayer為異步顯示模式(即使只是簡單的文本或圖片)這將幫你提高FPS。



我嘗試解釋了iOS繪圖系統(tǒng)(沒有使用OpenGL,因為他的情況更少)的主要思路。

當(dāng)然有些看起來很模糊,但事實上這只是一些方向,你應(yīng)該朝著這些方向來檢查你的代碼以找出影響滾動性能的所有問題。具體情況具體分期,但原則是不變的。

獲取完美平滑滾動的關(guān)鍵是非常特殊的代碼,他能讓你竭盡iOS的能力讓你的應(yīng)用更加平滑。完。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容