UITableView 是咱們開發中經常使用到的控件。其優化也是老生常談的話題。筆者在這裏拋磚引玉。html
IM模塊的頭像, 筆者的項目用UIButton。ios
早就據說iOS 設置圓角會形成性能上的開銷。設置cornerRadius和masksToBounds 會發生離屏渲染。swift
但在iOS 9後,蘋果對圓角問題進行了優化。UIImageView裏png圖片經過以上屬性設置圓角不會觸發離屏渲染(在iOS 12.1下親測)。但UIButton設置圖片和圓角,UILabel設置layer.backgroundColor 均會形成離屏渲染。數組
這方法會致使內存暴增,還會離屏渲染。並無優化,反而惡化了。bash
此方法用CPU渲染。CPU渲染能力不如GPU,但圓角這種輕量級渲染,CPU仍是能勝任的。重點是GPU離屏渲染須要上下文切換,嚴重時會形成卡頓。異步
此方法缺點,CPU以及內存 額外開銷。oop
這裏說的並非cell的複用,而是cell中部分view 的複用。佈局
IM模塊中,消息發送狀態view,有三種狀況,發送中圈圈,發送失敗感嘆號,發送成功沒有發送狀態view。性能
因爲大多數消息都是發送成功的,因此有 發送狀態view 的cell比較少,一個界面可能最多就一兩個狀態view。每一個cell都建立會浪費內存。
(固然平常項目,UIScrollView同樣的view也能夠相似思路優化)
思路:新建一個類ViewCache。兩個數組,一個裝着正在用的view,另外一個裝着緩存中的view。
當cell設置model時,若是發送失敗狀態,cell沒有statusView,就取緩存數組取,緩存數組空就新建一個,而且放到正在用的view數組中。當不用時,就放回緩存數組中。
- (void)setModel:(CellModel *)model {
switch (model.status) {
case 失敗:
if (!self.statusView) {
self.statusView = [self.viewCache dequeueStatusView]
[self.contentView addSubView:self.statusView];
}
self.statusView.frame = model.layout.statusViewFrame;
break;
case 發送中:
// 差很少
break;
default:
if (self.statusView) {
[self.viewCache removeStatusView:self.statusView];
[self.statusView removeFromSuperview];
}
break;
}
}
複製代碼
先來了解數據源、代理方法的調用時機。
網上有文章iOS開發-簡單科普下UITableView和UICollectionView代理執行順序說heightForRowAtIndexPath 在 cellForRowAtIndexPath前。筆者下載Demo來測試確實如此。
但筆者本身寫了一份Demo,親測並非。
因而在一篇文章tableView代理方法執行順序中發現真相。
其實文檔中也說清了,實現了預期高度,實際高度方法會被延遲到 cell將要顯示時 調用。
咱們知道 實現代理方法後, rowHeight 會失效。因此筆者僞一下代碼(固然無憑無證亂猜)
if(self.delegate && [self.delegate respondsToSelector:@selector(tableView:cellForRowAtIndexPath:)]) {
return [self.delegate tableView:self cellForRowAtIndexPath:indexPath];
} else {
return self.rowHeight;// 默認高度44
}
複製代碼
因此咱們也就能節省兩個方法(respondsToSelector和高度方法)的開銷。(蘋果有沒有針對這部分作優化不得而知)
動態高度有兩種方法,一種是利用AutoLayout,另外一種是直接算frame。
簡單的說,設置預算高度和estimatedRowHeight = UITableViewAutomaticDimension,而後cell中最下面的控件設置底部約束,撐開cell。
這方法不用實現高度代理方法,滑動條會在滾動過程當中從新調整。
可是AutoLayout最終須要轉成frame。這裏就無可避免開銷比直接算frame大。因此若是cell很複雜,不建議用AutoLayout。
緩存高度須要用如下兩個方法,返回Auto Layout後內容高度。
- (CGSize)systemLayoutSizeFittingSize:(CGSize)targetSize NS_AVAILABLE_IOS(6_0);
- (CGSize)systemLayoutSizeFittingSize:(CGSize)targetSize withHorizontalFittingPriority:(UILayoutPriority)horizontalFittingPriority verticalFittingPriority:(UILayoutPriority)verticalFittingPriority NS_AVAILABLE_IOS(8_0);
複製代碼
具體實現
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
CellModel * model = self.models[indexPath.row];
return model.cellHeight ?: UITableViewAutomaticDimension;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
CellModel * model = self.models[indexPath.row];
TestCell * cell = [TestCell cellForTableView:tableView model:model];
//高度緩存
if (!model.cellHeight) {
CGFloat height = [cell systemLayoutSizeFittingSize:CGSizeMake(tableView.frame.size.width, 0) withHorizontalFittingPriority:UILayoutPriorityRequired verticalFittingPriority:UILayoutPriorityFittingSizeLevel].height;
model.cellHeight = height;
}
return cell;
}
複製代碼
在model中,設置和佈局相關的屬性,cellHeight懶加載。(筆者項目封裝了一個CellLayout對象,包含每一個控件的frame以及cell高度)
- (CGFloat)cellHeight {
if (!_cellHeight) {
CGFloat iconH = 20;
CGFloat contentH = [self contentH];//算出來
_cellHeight = iconH + contentH + 10;
}
return _cellHeight;
}
複製代碼
而後在代理方法中
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
CellModel *model = self.models[indexPath.row];
return model.cellHeight;
}
複製代碼
iOS開發之UITableview之多種Cell高度自適應實現方案的UI流暢度分析
筆者能力有限,除了以上的優化策略,其實UITableView還有不少能優化的地方。之後筆者若是有機會,會嘗試往如下方向優化。