性能優(yōu)化之UITableView

1. Cell重用

  • 數(shù)據(jù)源方法優(yōu)化
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;

在可見的頁面會(huì)重復(fù)繪制頁面,每次刷新顯示都會(huì)去創(chuàng)建新的 Cell,非常耗費(fèi)性能。

解決方案:首先創(chuàng)建一個(gè)靜態(tài)變量 reuseID(代理方法返回 Cell 會(huì)調(diào)用很多次,防止重復(fù)創(chuàng)建,static 保證只會(huì)被創(chuàng)建一次,提高性能),然后,從緩存池中取相應(yīng) identifier 的Cell并更新數(shù)據(jù),如果沒有,才開始 alloc 新的 Cell,并用 identifier 標(biāo)識(shí) Cell。每個(gè) Cell 都會(huì)注冊(cè)一個(gè) identifier(重用標(biāo)識(shí)符)放入緩存池,當(dāng)需要調(diào)用的時(shí)候就直接從緩存池里找對(duì)應(yīng)的 id,當(dāng)不需要時(shí)就放入緩存池等待調(diào)用。(移出屏幕的 Cell 才會(huì)放入緩存池中,并不會(huì)被 release)所以在數(shù)據(jù)源方法中做出如下優(yōu)化:

// 調(diào)用次數(shù)太多,static 保證只創(chuàng)建一次 reuseID,提高性能
static NSString *reuseID = “reuseCellID”;
// 緩存池中取已經(jīng)創(chuàng)建的 cell
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseID];
  • 緩存池的實(shí)現(xiàn)
    當(dāng) Cel l要 alloc 時(shí),UITableView 會(huì)在堆中開辟一段內(nèi)存以供 Cell 緩存之用。Cell 的重用通過 identifier 標(biāo)識(shí)不同類型的 Cell ,由此可以推斷出,緩存池外層可能是一個(gè)可變字典,通過key來取出內(nèi)部的 Cell,而緩存池為存儲(chǔ)不同高度、不同類型(包含圖片、Label 等)的 Cell,可以推斷出緩存池的字典內(nèi)部可能是一個(gè)可變數(shù)組,用來存放不同類型的 Cell,緩存池中只會(huì)保存已經(jīng)被移出屏幕的不同類型的 Cell。

  • 緩存池獲取可重用Cell兩個(gè)方法的區(qū)別

-(nullable __kindof UITableViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier;

這個(gè)方法會(huì)查詢可重用Cell,如果注冊(cè)了原型Cell,能夠查詢到,否則,返回nil;而且需要判斷if(cell == nil),才會(huì)創(chuàng)建Cell,不推薦

-(__kindof UITableViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath NS_AVAILABLE_IOS(6_0);

使用這個(gè)方法之前,必須通過xib(storyboard)或是Class(純代碼)注冊(cè)可重用Cell,而且這個(gè)方法一定會(huì)返回一個(gè)Cell注冊(cè)Cell

- (void)registerNib:(nullable UINib *)nib forCellReuseIdentifier:(NSString *)identifier NS_AVAILABLE_IOS(5_0);
- (void)registerClass:(nullable Class)cellClass forCellReuseIdentifier:(NSString *)identifier NS_AVAILABLE_IOS(6_0);

好處:如果緩沖區(qū) Cell 不存在,會(huì)使用原型 Cell 實(shí)例化一個(gè)新的 Cell,不需要再判斷,同時(shí)代碼結(jié)構(gòu)更清晰。

2. 定義一種(盡量少)類型的 Cell 及善用 hidden 隱藏(顯示) subviews

  • 一種類型的 Cell 分析 Cell 結(jié)構(gòu),盡可能的將相同內(nèi)容的抽取到一種樣式 Cell 中,前面已經(jīng)提到了 Cell 的重用機(jī)制,這樣就能保證 UITbaleView 要顯示多少內(nèi)容,真正創(chuàng)建出的Cell 可能只比屏幕顯示的 Cell 多一點(diǎn)。雖然 Cell 的 體積 可能會(huì)大點(diǎn),但是因?yàn)?Cell 的數(shù)量不會(huì)很多,完全可以接受的。
    好處:

    • 減少代碼量,減少 Nib 文件的數(shù)量,統(tǒng)一一個(gè) Nib 文件定義 Cell,容易修改、維護(hù)
    • 基于 Cell 的重用,真正運(yùn)行時(shí)鋪滿屏幕所需的 Cell 數(shù)量大致是固定的,設(shè)為 N 個(gè)。所以如果如果只有一種 Cell ,那就是只有 N 個(gè) Cell 的實(shí)例;但是如果有 M 種 Cell,那么運(yùn)行時(shí)最多可能會(huì)是M x N = MN個(gè) Cell 的實(shí)例,雖然可能并不會(huì)占用太多內(nèi)存,但是能少點(diǎn)不是更好嗎。
  • 善用 hidden 隱藏(顯示) subviews 只定義一種 Cell,那該如何顯示不同類型的內(nèi)容呢?
    答案就是,把所有不同類型的 view 都定義好,放在 cell 里面,通過 hidden 顯示、隱藏,來顯示不同類型的內(nèi)容。畢竟,在用戶快速滑動(dòng)中,只是單純的顯示、隱藏 subview 比實(shí)時(shí)創(chuàng)建要快得多。

3 提前計(jì)算并緩存Cell的高度

在 iOS 中,不設(shè) UITableViewCell 的預(yù)估行高的情況下,會(huì)優(yōu)先調(diào)用 tableView:heightForRowAtIndexPath: 方法,獲取每個(gè) Cell 的即將顯示的高度,從而確定 UITableView 的布局,實(shí)際就是要獲取 contentSize(UITableView 繼承自 UIScrollView,只有獲取滾動(dòng)區(qū)域,才能實(shí)現(xiàn)滾動(dòng)),然后才調(diào)用 tableView:cellForRowAtIndexPath,獲取每個(gè) Cell,進(jìn)行賦值。如果項(xiàng)目中模塊有 10000個(gè) Cell 需要顯示,可想而知…

解決方案:我個(gè)人認(rèn)為,可以創(chuàng)建一個(gè) frame 模型,提前計(jì)算每個(gè) Cell 的高度。參考其中一篇博客的時(shí)候,在解決這個(gè)問題的時(shí)候,可以將計(jì)算 Cell 的高度放入數(shù)據(jù)模型,但這與 MVC 設(shè)計(jì)模式可能稍微有點(diǎn)沖突,這個(gè)時(shí)候我就想到 MVVM 這種設(shè)計(jì)模式,這個(gè)時(shí)候才能稍微有點(diǎn) MVVM 這種設(shè)計(jì)模式的優(yōu)點(diǎn)(其實(shí)還是很不理解的),可以講計(jì)算 Cell 高度放入 ViewModel(視圖模型)中,讓Model(數(shù)據(jù)模型)只負(fù)責(zé)處理數(shù)據(jù)。

4 異步繪制(自定義 Cell 繪制)

遇到比較復(fù)雜的界面的時(shí)候,如復(fù)雜點(diǎn)的圖文混排,上面的那種優(yōu)化行高的方式可能就不能滿足要求了,當(dāng)然了,由于我的開發(fā)經(jīng)驗(yàn)尚短,說實(shí)話,還沒遇到要將自定義的 Cell 重新繪制

5 滑動(dòng)時(shí),按需加載

開發(fā)的過程中,自定義 Cell 的種類千奇百怪,但 Cell 本來就是用來顯示數(shù)據(jù)的,不說 100%帶有圖片,也差不多,這個(gè)時(shí)候就要考慮,下滑的過程中可能會(huì)有點(diǎn)卡頓,尤其網(wǎng)絡(luò)不好的時(shí)候,異步加載圖片是個(gè)程序員都會(huì)想到,但是如果給每個(gè)循環(huán)對(duì)象都加上異步加載,開啟的線程太多,一樣會(huì)卡頓,我記得好像線程條數(shù)一般 3-5 條,最多也就 6 條吧。這個(gè)時(shí)候利用 UIScrollViewDelegate 兩個(gè)代理方法就能很好地解決這個(gè)問題。

- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView

6.緩存View

當(dāng) Cell 中的部分 View 是非常獨(dú)立的,并且不便于重用的,而且 體積 非常小,在內(nèi)存可控的前提下,我們完全可以將這些view緩存起來。當(dāng)然也是緩存在模型中。

7.避免大量的圖片縮放、顏色漸變等,盡量顯示“大小剛好合適的圖片資源”

8.避免同步的從網(wǎng)絡(luò)、文件獲取數(shù)據(jù),Cell 內(nèi)實(shí)現(xiàn)的內(nèi)容來自 web,使用異步加載,緩存請(qǐng)求結(jié)果

9.渲染

  • 減少 subviews 的個(gè)數(shù)和層級(jí)子控件的層級(jí)越深,渲染到屏幕上所需要的計(jì)算量就越大;如多用 drawRect 繪制元素,替代用 view 顯示
  • 少用 subviews 的透明圖層對(duì)于不透明的 View,設(shè)置 opaque 為 YES,這樣在繪制該 View時(shí),就不需要考慮被View覆蓋的其他內(nèi)容(盡量設(shè)置 Cell 的 view 為 opaque,避免 GPU 對(duì) Cell下面的內(nèi)容也進(jìn)行繪制)
  • 避免CALayer特效(shadowPath)給Cell中View加陰影會(huì)引起性能問題,如下面代碼會(huì)導(dǎo)致滾動(dòng)時(shí)有明顯的卡頓:
view.layer.shadowColor = color.CGColor;
view.layer.shadowOffset = offset;
view.layer.shadowOpacity = 1;
view.layer.shadowRadius = radius;
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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