美文网首页
UITableView 优化

UITableView 优化

作者: Hsusue | 来源:发表于2019-03-09 14:21 被阅读2次

    前言

    UITableView 是我们开发中常用到的控件。其优化也是老生常谈的话题。笔者在这里抛砖引玉。

    image

    圆角问题

    IM模块的头像, 笔者的项目用UIButton。

    早就听说iOS 设置圆角会造成性能上的开销。设置cornerRadius和masksToBounds 会发生离屏渲染。

    但在iOS 9后,苹果对圆角问题进行了优化。UIImageView里png图片通过以上属性设置圆角不会触发离屏渲染(在iOS 12.1下亲测)。但UIButton设置图片和圆角,UILabel设置layer.backgroundColor 均会造成离屏渲染。

    image

    iOS 高效添加圆角效果实战讲解

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

    这方法会导致内存暴增,还会离屏渲染。并没有优化,反而恶化了。

    • 还有一种方法,Core Graphics画出圆角矩形,UIImageView直接截取圆角图片。

    此方法用CPU渲染。CPU渲染能力不如GPU,但圆角这种轻量级渲染,CPU还是能胜任的。重点是GPU离屏渲染需要上下文切换,严重时会造成卡顿。

    此方法缺点,CPU以及内存 额外开销。


    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,亲测并不是。

    image

    于是在一篇文章tableView代理方法执行顺序中发现真相。

    image

    其实文档中也说清了,实现了预期高度,实际高度方法会被延迟到 cell将要显示时 调用。


    image

    对于固定高度的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。

    参考

    相关文章

      网友评论

          本文标题:UITableView 优化

          本文链接:https://www.haomeiwen.com/subject/pyvspqtx.html