這篇文章是一個UITableView
的優(yōu)化過程穴店,即優(yōu)化過程工具的使用。環(huán)境是Xcode11.6版本。
基本需求:
1苇侵、可以根據 JSON
文件配置設置 cell
的顯示類型和 cell
的一些基本樣式的修改油狂,這樣需求下對 cell
實現历恐,沒有想到好的方法,采用最笨的方法专筷,就是 cell.contentView
上添加的 view
的方式弱贼。而view
根據 cellModel
的 templateName
、data
和style
傳到 factory(工廠)
的形式生成磷蛹。 cell.contentView
添加之前需要先上移除所有子視圖,再重新創(chuàng)建 View
添加到 cell.contentView
上吮旅。
下面是view
根據 cellModel
的 templateName
、data
和style
傳到 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
即新增的cell
的IndexPath
;
新增的cell
的IndexPath
統(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
自帶的工具Instruments
(Comd + i
快捷鍵)敌蜂,檢查3步走,如圖1津肛、2章喉、3所示
查看結果如圖4所示,可以雙擊點擊泄漏的方法進行查看代碼,結果如圖5身坐。
不過個人感覺不是很好用(可能是不會用 - _ -!!! )秸脱,推薦個好用的第三方庫 ,騰訊的 MLeaksFinder
, 可以直接 pod 'MLeaksFinder'
但是這個庫因為很久沒有維護了部蛇,里面使用了 UIAlertView
,導致每次有泄漏就崩潰摊唇, 因此我就自己修改了,自己上傳了一下 pod
名字為 HJ_MLeaksFinder
(MLeaksFinder
的 copy
版本,不喜勿噴), 可以使用下面的進行 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;
得出結論:滑動中,
tableView
的contentSize
突然變化會出現跳動释树,這個時候就需要設置estimatedRowHeight
肠槽,而且這個值越接近 真實高度越好。
解決方案:設置
estimatedRowHeight
躏哩,在tableView
的willDisplayCell
方法署浩,進行設置,這個方法還包括預加載的操作判斷- (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)化酗失。一點小小學習心得和使用體驗
使用
Instruments
(Comd + i
快捷鍵打開) 檢查义钉,
這個檢查耗時比檢查內存泄漏好用多了,也是比較簡單的规肴。因為可以直接定位到代碼断医,方便快捷。
在性能和執(zhí)行速度上有一個小的建議提供給小伙伴奏纪;
就是大的方法鉴嗤,沒有拆分為小的函數方法性能好,所以不要寫大的方法序调,盡量的拆分為小的方法塊吧醉锅。
下面還有一個檢查代碼耗時的小方法,可以檢查某一行代碼或幾行代碼的耗時時間发绢。
CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent(); /// 需要檢查的耗時代碼 CFAbsoluteTime endTime = (CFAbsoluteTimeGetCurrent() - startTime); NSLog(@"linkTime %f ms", endTime * 1000.0); // 打印耗時時間
好了硬耍,本次分享就到這里了,因為平時很少寫文章边酒,如果哪里寫的不對经柴,不好,就請大家多多指教墩朦,多多留言坯认。非常感謝!Cセ痢牛哺!。
^ 0_0 ^ -- Bright:祝大家開心快樂每一天劳吠。