UITableView 列表滑動優(yōu)化心得

這篇文章是一個UITableView的優(yōu)化過程穴店,即優(yōu)化過程工具的使用。環(huán)境是Xcode11.6版本。

基本需求:

1苇侵、可以根據 JSON 文件配置設置 cell 的顯示類型和 cell 的一些基本樣式的修改油狂,這樣需求下對 cell實現历恐,沒有想到好的方法,采用最笨的方法专筷,就是 cell.contentView 上添加的 view的方式弱贼。而view根據 cellModeltemplateNamedatastyle傳到 factory(工廠) 的形式生成磷蛹。 cell.contentView 添加之前需要先上移除所有子視圖,再重新創(chuàng)建 View 添加到 cell.contentView 上吮旅。
下面是view根據 cellModeltemplateNamedatastyle傳到 factory(工廠) 的形式生成核心代碼(不是重點)

- (UIView *)setContentViewByModel:(CellBaseModel *)model indexPath:(NSIndexPath *)indexPath{
    NSDictionary *dic = @{@"templateName":model.templateName,@"data":model};
    __weak typeof(self)  weakSelf = self;
    CellBaseView *view = (CellBaseView *)[[UITemplateManager sharedInstance] viewWithTemplateData:dic  constraintWidth:self.view.frame.size.width];
    view.closeBlock = ^(UIView * _Nonnull actionView) {
        [weakSelf setHomePopoViewFromActionView:actionView indexPath:indexPath model:model];
    };
    return  view;
}

2味咳、需要預加載鸟辅,就是在滑動 tableView 列表時氛什,即將顯示完的時候,提前加載數據匪凉,體驗無縫加載枪眉,沒有等待。

cell高度處理

1再层、tableView 使用的 UITableView+FDTemplateLayoutCell 來計算高度贸铜,并緩存高度處理。

 CGFloat height = [self fd_heightForCellWithIdentifier:cellId 
                                      cacheByIndexPath:indexPath
                                         configuration:^(id cell) {
     XXXXXXX // cell的配置和賦值
}];
return height;

使用UITableView+FDTemplateLayoutCell 優(yōu)點是使用Layout來計算高度并緩存聂受,不用每次都計算蒿秦,雖然系統(tǒng)有UITableViewAutomaticDimension也是Layout計算高度的,但是并沒有緩存機制蛋济,所以性能上也就略有缺陷棍鳖。
使用UITableView+FDTemplateLayoutCell時,如果界面比較復雜碗旅,而且高度變化比較多渡处,建議使用frame計算并存儲在dataModel中,因為UITableView+FDTemplateLayoutCell計算高度主要是用系統(tǒng)的方法[cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;來計算的祟辟。
UITableView+FDTemplateLayoutCell也可以使用frame計算高度医瘫,只需要設置屬性fd_enforceFrameLayout=YES
UITableView+FDTemplateLayoutCell 有一個缺點,如何布局有些異步操作旧困,那計算高度將會不準確醇份。需要注意
2、加載數據使用 AFNetworking 加載數據吼具。

tableView 常見的卡頓原因

卡頓最常見的網上的文章比較多僚纷,就不一一列舉了,列舉幾個常見的原因:

  • 計算高度比較耗時
  • 主線程加載圖片或進行耗時操作
  • 視圖布局比較復雜拗盒,有大量的離屏渲染或設置alpha
  • 內存暴漲
  • ……

tableView滑動卡頓

  • 問題分析1:

我們就逐步分析怖竭,首先看計算高度,因為使用UITableView+FDTemplateLayoutCell計算高度锣咒,應該不會存在重復計算問題,但是防止預防萬一赞弥,還是在
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath 代理方法中 UITableView+FDTemplateLayoutCell計算高度的方法打印 log日志毅整,發(fā)現打印日志每次都會重 indexPath.row=0開始,也就是說每次加載更多绽左,更新數據都會從 0 行重新計算悼嫉,那這樣計算肯定越來越多,那我們就想拼窥,難道和加載數據戏蔑,更新數據有關嗎蹋凝?為什么每次都從0開始計算呢?經過多次查閱資料总棵,發(fā)現[self.tableView reloadData]這個方法刷新造成的鳍寂。因為這個方法是重新加載數據,既然重新加載了所有數據情龄,那么UITableView+FDTemplateLayoutCell就不知道數據是否有變更迄汛,為了保證高度的準確,只能從0再全部計算一遍骤视。

得出結論:加載更多時鞍爱,不能使用 [self.tableView reloadData] 因為 reloadData 會導致 UITableView+FDTemplateLayoutCell 每次都重 0 行開始計算,意味著計算高度的時間越來越久专酗,而且多次重復計算睹逃,嚴重浪費性能。從而引發(fā)卡頓祷肯。

解決方案:加載更多不使用[self.tableView reloadData]沉填,使用另外一個[self.tableView insertRowsAtIndexPaths:indexpaths withRowAnimation:UITableViewRowAnimationNone]

[self.tableView insertRowsAtIndexPaths:indexpaths withRowAnimation:UITableViewRowAnimationNone];

其中indexpaths即新增的 cellIndexPath;
新增的 cellIndexPath統(tǒng)計方法

NSMutableArray *indexPaths = @[].mutableCopy;
for (int i = 0; i < models.count; i++) {
     NSIndexPath *indexPath = [NSIndexPath indexPathForRow:self.dataArray.count + i inSection:0];
     [indexPaths addObject:indexPath];
 }

[self.tableView insertRowsAtIndexPaths:indexpaths withRowAnimation:UITableViewRowAnimationNone]這句話可能會有動畫效果, 雖然用了 ·UITableViewRowAnimationNone· 但是還是可能有動畫躬柬,如何不需要動畫可以用下面的方法

[UIView performWithoutAnimation:^{
     [self.tableView insertRowsAtIndexPaths:indexpaths withRowAnimation:UITableViewRowAnimationNone];
}];

這樣寫可以沒有動畫拜轨,如果需要有動畫,不是系統(tǒng)的動畫允青,可以使用

[self.tableView beginUpdates];
/// 添加自己的動畫 code
[self.tableView insertRowsAtIndexPaths:indexpaths withRowAnimation:UITableViewRowAnimationNone];
/// 添加自己的動畫 code
[self.tableView endUpdates];
  • 注意 [self.tableView beginUpdates] 橄碾,[self.tableView endUpdates] 成對出現,中間是動畫代碼颠锉。 reloadData 方法不能使用該方法法牲。
  • 問題分析2:

首先首頁布局比較簡單常見,圖片數量不多琼掠,并且加載圖片是采用 SDWebImage異步加載拒垃,不會出現加載大圖片卡主線程問題,圖片的切圓角和陰影也不多(有幾個)瓷蛙,離屏渲染也不多悼瓮,也不會有性能問題。然后看一下內存艰猬。使用xcode查看即可横堡。

查看內存

得出結論:通過查看內存,發(fā)現內存增加比較快冠桃,簡直就是暴漲命贴,斷定內存。
解決方案:查找內存泄漏,解決內存泄漏胸蛛。
網上查找內存泄漏的方法方案比較多污茵,我們就選兩個,第一個是xcode系統(tǒng)工具 Instruments
Instruments功能巨大葬项,可以查看耗時泞当,CPU內存分配,內存泄漏玷室,僵尸對象等等零蓉,有很多文章,這里就不一一列舉了穷缤。

打開查看內存泄漏的工具 xcode 自帶的工具 InstrumentsComd + i 快捷鍵)敌蜂,檢查3步走,如圖1津肛、2章喉、3所示

圖1

圖2

圖3

查看結果如圖4所示,可以雙擊點擊泄漏的方法進行查看代碼,結果如圖5身坐。
圖4

圖5

不過個人感覺不是很好用(可能是不會用 - _ -!!! )秸脱,推薦個好用的第三方庫 ,騰訊的 MLeaksFinder , 可以直接 pod 'MLeaksFinder' 但是這個庫因為很久沒有維護了部蛇,里面使用了 UIAlertView ,導致每次有泄漏就崩潰摊唇, 因此我就自己修改了,自己上傳了一下 pod 名字為 HJ_MLeaksFinder(MLeaksFindercopy 版本,不喜勿噴), 可以使用下面的進行 pod 添加 涯鲁,需要使用 1.0.2 版本巷查。

pod 'HJ_MLeaksFinder' 

這個庫真的真的真的很好用,因為不許要做任何操作抹腿,除了單利需要添加- (BOOL)willDealloc不需要釋放之外岛请,其他地方頭文件都不需要導入,有內存泄漏就可以直接彈窗彈出來警绩,還可以查詢循環(huán)引用(MLeaksFinder加上 pod ‘FBRetainCycleDetector’ 進行配合使用 而 HJ_MLeaksFinder 內部集成了 FBRetainCycleDetector)崇败。但是有些地方泄漏可能是強引用了,并沒有循環(huán)肩祥,所以會檢測不出來后室。

經過多次的內存泄漏提示彈窗,找到了內存泄漏的地方混狠,只是強引用岸霹,是使用cell.contentView上的子視圖View后,再次布局cell.contentView需要移除cell.contentView上的子視圖View檀蹋。而使用的移除方法是

- (void)removeAllSubviews
{
   while (self.subviews.count) {
       [self.subviews.lastObject removeFromSuperview];
   }
}

大多數會使用這個方法來移除所有子視圖松申,但是有可能移除的不夠完整,不夠干凈俯逾。當這個view有子視圖有內容時贸桶,可能會放在了內存中,只是沒有顯示出來桌肴,如果查看內存皇筛,可以發(fā)現內存會有大量的增加,這個時候就需要移除所有的坠七,包括子視圖上的子視圖水醋。

- (void)removeAllSubviews:(UIView *)supView
{
   while (supView.subviews.count) {
       UIView *subView = supView.subviews.lastObject;
       if (subView.subviews.count > 0) {
           [self removeAllSubviews:subView];
       }
       else {
           [subView removeFromSuperview];
           subView = nil;
       }
   }
}

類似這樣的方法,遞歸移除彪置。(這是個人想的拄踪,可能不是很友好,大家有好的想法可以留言拳魁,謝謝)

  • 上面的問題都解決完之后惶桐,還有加載更多數據加載完成刷新頁面是時會出現卡頓,或是閃動潘懊。

問題分析:
計算高度已經沒有了問題姚糊,內存也沒有了問題,現在為什么還會卡頓(閃動)呢授舟,
網上搜索問題救恨,大多數解決方法是

self.tableView.estimatedRowHeight = 0;
self.tableView.estimatedSectionHeaderHeight = 0;
self.tableView.estimatedSectionFooterHeight = 0;

得出結論:滑動中,tableViewcontentSize 突然變化會出現跳動释树,這個時候就需要設置 estimatedRowHeight肠槽,而且這個值越接近 真實高度越好。

解決方案:設置 estimatedRowHeight躏哩,在tableViewwillDisplayCell方法署浩,進行設置,這個方法還包括預加載的操作判斷

- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
  CGFloat cellHeight = 0.0;
   if ([self.fd_indexPathHeightCache existsHeightAtIndexPath:indexPath]) {
        cellHeight = [self.fd_indexPathHeightCache heightForIndexPath:indexPath];
   }
   else {
        if (cellHeight == 0) {
            if (indexPath.row > 1) {
                 NSIndexPath *index = [NSIndexPath indexPathForRow:indexPath.row - 1 inSection:indexPath.section];
                 cellHeight = [tableView rectForRowAtIndexPath:index].size.height;
            }
        }
   }
   self.estimatedRowHeight = cellHeight;
     if (!self.isLoading) {  // 防止多次聯(lián)系加載更多
         if (self.dataArray.count - number <= indexPath.row) { // number 還有多少條就可開始預加載
             [self loadMore]; // 在加載更多 loadMore 中  isLoading 設置為 YES 扫尺,再加載完成之后再設置為NO
         }
     }
}

在滑動過程中由于cell的高度變化導致出現的跳動筋栋,實現這個方法可以讓計算高度方法不用一次性全部數據計算(一頁的數據或全部數據),只有到了即將顯示的cell正驻,才計算cell的高度

  • 新的問題:設置了 tableView.estimatedRowHeight 緩慢滑動可能會出現tableView 突然的跳動

問題分析:
網上大多數解釋說弊攘,設置了 estimatedRowHeight ,使 tableView 不知道具體的 contentsize姑曙,需要滾動完才會知道襟交。

得出結論:因為設置了 estimatedRowHeight 不是 0 ,使 tableView不能完美的知道 contentsize伤靠,從而是tableView滾動閃動或跳動捣域。

解決方案:沒有想到好的解決方案。就使用 scrollView 滾動代理方案設置,可是并不完美焕梅,
監(jiān)聽 scrollView 滾動方法

- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
   self.tableView.isSetEstimatedHeight = scrollView.isDecelerating;
}

這個方法迹鹅,self.tableView.isSetEstimatedHeight 這個是自定義的是否設置預設高度開關,當快速滑動時 scrollView.isDecelerating 是 1 (YES)緩慢滑動是 0 (NO)贞言,來解決緩慢滑動不需要estimatedRowHeight和快速滑動需要estimatedRowHeight的問題
【如果您有更好的解決方法斜棚,請留下解決方法,非常感謝该窗〉苁矗】

  • 檢查性能(耗時),針對性優(yōu)化酗失。一點小小學習心得和使用體驗

使用 InstrumentsComd + i快捷鍵打開) 檢查义钉,

圖6

圖7

圖8

這個檢查耗時比檢查內存泄漏好用多了,也是比較簡單的规肴。因為可以直接定位到代碼断医,方便快捷。

  • 在性能和執(zhí)行速度上有一個小的建議提供給小伙伴奏纪;就是大的方法鉴嗤,沒有拆分為小的函數方法性能好,所以不要寫大的方法序调,盡量的拆分為小的方法塊吧醉锅。

  • 下面還有一個檢查代碼耗時的小方法,可以檢查某一行代碼或幾行代碼的耗時時間发绢。

CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();
/// 需要檢查的耗時代碼
CFAbsoluteTime endTime = (CFAbsoluteTimeGetCurrent() - startTime);
NSLog(@"linkTime %f ms", endTime * 1000.0);   // 打印耗時時間

好了硬耍,本次分享就到這里了,因為平時很少寫文章边酒,如果哪里寫的不對经柴,不好,就請大家多多指教墩朦,多多留言坯认。非常感謝!Cセ痢牛哺!。

^ 0_0 ^ -- Bright:祝大家開心快樂每一天劳吠。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末引润,一起剝皮案震驚了整個濱河市袖订,隨后出現的幾起案子塑顺,更是在濱河造成了極大的恐慌嵌牺,老刑警劉巖蒙保,帶你破解...
    沈念sama閱讀 210,978評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異奴曙,居然都是意外死亡褒脯,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 89,954評論 2 384
  • 文/潘曉璐 我一進店門缆毁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人到涂,你說我怎么就攤上這事脊框。” “怎么了践啄?”我有些...
    開封第一講書人閱讀 156,623評論 0 345
  • 文/不壞的土叔 我叫張陵浇雹,是天一觀的道長。 經常有香客問我屿讽,道長昭灵,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,324評論 1 282
  • 正文 為了忘掉前任伐谈,我火速辦了婚禮烂完,結果婚禮上,老公的妹妹穿的比我還像新娘诵棵。我一直安慰自己抠蚣,他們只是感情好,可當我...
    茶點故事閱讀 65,390評論 5 384
  • 文/花漫 我一把揭開白布履澳。 她就那樣靜靜地躺著嘶窄,像睡著了一般。 火紅的嫁衣襯著肌膚如雪距贷。 梳的紋絲不亂的頭發(fā)上柄冲,一...
    開封第一講書人閱讀 49,741評論 1 289
  • 那天,我揣著相機與錄音忠蝗,去河邊找鬼现横。 笑死,一個胖子當著我的面吹牛阁最,可吹牛的內容都是我干的长赞。 我是一名探鬼主播,決...
    沈念sama閱讀 38,892評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼闽撤,長吁一口氣:“原來是場噩夢啊……” “哼得哆!你這毒婦竟也來了?” 一聲冷哼從身側響起哟旗,我...
    開封第一講書人閱讀 37,655評論 0 266
  • 序言:老撾萬榮一對情侶失蹤贩据,失蹤者是張志新(化名)和其女友劉穎栋操,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體饱亮,經...
    沈念sama閱讀 44,104評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡矾芙,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了近上。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片剔宪。...
    茶點故事閱讀 38,569評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡壹无,死狀恐怖葱绒,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情斗锭,我是刑警寧澤地淀,帶...
    沈念sama閱讀 34,254評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站岖是,受9級特大地震影響帮毁,放射性物質發(fā)生泄漏。R本人自食惡果不足惜豺撑,卻給世界環(huán)境...
    茶點故事閱讀 39,834評論 3 312
  • 文/蒙蒙 一烈疚、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧聪轿,春花似錦胞得、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至危号,卻和暖如春牧愁,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背外莲。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評論 1 264
  • 我被黑心中介騙來泰國打工猪半, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人偷线。 一個月前我還...
    沈念sama閱讀 46,260評論 2 360
  • 正文 我出身青樓磨确,卻偏偏與公主長得像,于是被迫代替她去往敵國和親声邦。 傳聞我的和親對象是個殘疾皇子乏奥,可洞房花燭夜當晚...
    茶點故事閱讀 43,446評論 2 348

推薦閱讀更多精彩內容