UITableView 優化

前言

UITableView 是咱們開發中經常使用到的控件。其優化也是老生常談的話題。筆者在這裏拋磚引玉。html


圓角問題

IM模塊的頭像, 筆者的項目用UIButton。ios

早就據說iOS 設置圓角會形成性能上的開銷。設置cornerRadius和masksToBounds 會發生離屏渲染。swift

但在iOS 9後,蘋果對圓角問題進行了優化。UIImageView裏png圖片經過以上屬性設置圓角不會觸發離屏渲染(在iOS 12.1下親測)。但UIButton設置圖片和圓角,UILabel設置layer.backgroundColor 均會形成離屏渲染。數組

iOS 高效添加圓角效果實戰講解緩存

  • 一種方法是在drawRect中用CAShapeLayer 和 UIBezierPath。

這方法會致使內存暴增,還會離屏渲染。並無優化,反而惡化了。bash

  • 還有一種方法,Core Graphics畫出圓角矩形,UIImageView直接截取圓角圖片。

此方法用CPU渲染。CPU渲染能力不如GPU,但圓角這種輕量級渲染,CPU仍是能勝任的。重點是GPU離屏渲染須要上下文切換,嚴重時會形成卡頓。異步

此方法缺點,CPU以及內存 額外開銷。oop


cell中部分view的複用

這裏說的並非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代理執行順序heightForRowAtIndexPathcellForRowAtIndexPath前。筆者下載Demo來測試確實如此。

但筆者本身寫了一份Demo,親測並非。

因而在一篇文章tableView代理方法執行順序中發現真相。

其實文檔中也說清了,實現了預期高度,實際高度方法會被延遲到 cell將要顯示時 調用。

對於固定高度的cell,直接設置rowHeight,不要實現代理cell高度方法。

咱們知道 實現代理方法後, rowHeight 會失效。因此筆者僞一下代碼(固然無憑無證亂猜)

if(self.delegate && [self.delegate respondsToSelector:@selector(tableView:cellForRowAtIndexPath:)]) {
        return [self.delegate tableView:self cellForRowAtIndexPath:indexPath];
    } else {
        return self.rowHeight;// 默認高度44
    }
複製代碼

因此咱們也就能節省兩個方法(respondsToSelector和高度方法)的開銷。(蘋果有沒有針對這部分作優化不得而知)

對於動態高度的cell

動態高度有兩種方法,一種是利用AutoLayout,另外一種是直接算frame。

  • AutoLayout

iOS-談一談自適應Cell的高度緩存

簡單的說,設置預算高度和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;
}
複製代碼
  • 另外一種直接算frame。

在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還有不少能優化的地方。之後筆者若是有機會,會嘗試往如下方向優化。

  • 利用RunLoop空閒時間,預計算未顯示的Cell高度。(能夠參考SDWebImage)
  • 異步繪製Cell。
  • 滑動手指鬆開時,描繪計算要顯示的Cell。

參考

相關文章
相關標籤/搜索