美文网首页性能优化iOSIOS开发者学习笔记
TableView性能优化的9个常用方法

TableView性能优化的9个常用方法

作者: OneKeyV | 来源:发表于2016-11-18 00:55 被阅读370次
    tableView可以说是每个app中必不可少的控件,所以掌握流畅度优化技能相当的重要。

    这里总结一些常用的优化技巧,分享给大家:

    ① cell内部控件的层次结构尽量的少,可以使用drawRect画;

    ② 控件尽量不要有透明度,因为如果上层控件有透明度的话,系统会努力的绘制下层控件的内容与上层控件的内容,并且将两个内容按照透明度去进行绘制,十分耗性能;

    ③ 栅格化
    将cell内容渲染成一张图片,在滚动的时候就是一张图片:

    layer.shouldRasterize      =  true;   // 栅格化cell,滚动时只显示图片
    layer.rasterizationScale   =  [UIScreen mainScreen].scale;   // 默认缩放比例是1,要适配当前屏幕
    

    在Instruments中调式可以看到cell已经是黄色,说明已经渲染成一张图片:


    栅格化cell.gif

    ④ 异步绘制cell的layer,如果cell比较复杂时可以使用

    layer.drawsAsynchronously = true;
    官方文档注释:
    /* When true, the CGContext object passed to the -drawInContext: method
     * may queue the drawing commands submitted to it, such that they will
     * be executed later (i.e. asynchronously to the execution of the
     * -drawInContext: method). This may allow the layer to complete its
     * drawing operations sooner than when executing synchronously. The
     * default value is NO. */
    @property BOOL drawsAsynchronously
      CA_AVAILABLE_STARTING (10.8, 6.0, 9.0, 2.0);
    意思就是如果使用异步,cell会提前绘制。
    

    ⑤ 在cell中不要用layer去画圆角
    CALayer的cornerRadius是一个超级费性能的东西,它会在每一帧都裁剪圆角,无论你有没有滚动视图都会运算裁剪圆角,很费GPU性能!所以要让CPU去做圆角图片!
    可以在UIImage分类中,开启上下文,利用贝塞尔路径画圆角:

    #import "UIImage+Extension.h"
    
    @implementation UIImage (Extension)
    
    - (void)qv_cornerImageWithSize:(CGSize)size fillColor:(UIColor *)fillColor completion:(void (^)(UIImage *))completion {
    
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            UIGraphicsBeginImageContextWithOptions(size, true, 0);
            CGRect rect = CGRectMake(0, 0, size.width, size.height);
            [fillColor setFill];
            UIRectFill(rect);
            UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:rect];
            [path addClip];
            [self drawInRect:rect];
            UIImage *result = UIGraphicsGetImageFromCurrentImageContext();
            UIGraphicsEndImageContext();
            dispatch_async(dispatch_get_main_queue(), ^{
                if (result != nil) {
                    completion(result);
                }
            });
        });
    }
    
    @end
    

    参考文章
    http://www.cocoachina.com/ios/20150803/12873.html
    http://blog.csdn.net/shaobo8910/article/details/46779259
    也可以让服务器去处理圆角图片,这样我们就不需要再去操作。
    注意:iOS9.0之后,.png图片直接设置圆角是不会产生离屏渲染,iOS9.0之前还是会离屏渲染的。

    ⑥ 缓存行高
    如果是自动布局计算行高很消耗CPU,每次滚动到该cell都要计算self.contentView.layoutIfNeeded,注意要移除contentView的底部约束。建议复杂的cell不要用自动布局。

    ⑦ cell内部所有显示的数据提前准备好,尽量少实时计算。所有的控件大小提前计算好,不要每一次都计算。

    ⑧ 按需加载,按照用户滚动的速度去选择加载哪个cell
    原理:在快速滑动松手后滚动的cell个数超过预定的个数,只显示最后出现的cell的前三个cell,把这三个cell的indexPath存到数组中,在数据源方法里判断如果数组count>0,且数组不包含当前的indexPath,那就说明此cell是在快速滑动中需要隐藏的:
    代理方法:

    //按需加载 - 如果目标行与当前行相差超过指定行数,只在目标滚动范围的前后指定3行加载。
    - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset{
        NSIndexPath *ip = [_titleTableView indexPathForRowAtPoint:CGPointMake(0, targetContentOffset->y)];
        NSIndexPath *cip = [[_titleTableView indexPathsForVisibleRows] firstObject];
        NSInteger skipCount = 1;    // 这里我为了方便演示写的1,大家可以按需求自行设定
        if (labs(cip.row-ip.row)>skipCount) {
    //        此方法可以获取将要显示的组
    //        visibleSections = [NSSet setWithArray:[[_titleTableView indexPathsForVisibleRows] valueForKey:@"section"]]; 
            NSArray *temp = [_titleTableView indexPathsForRowsInRect:CGRectMake(0, targetContentOffset->y, _titleTableView.frame.size.width, _titleTableView.frame.size.height)];
            NSMutableArray *arr = [NSMutableArray arrayWithArray:temp];
            if (velocity.y<0) {      // 上滑
                NSIndexPath *indexPath = [temp lastObject];
                if (indexPath.row+3<numberOfRows*numberOfSections) {
                    [arr addObject:[NSIndexPath indexPathForRow:indexPath.row+3 inSection:indexPath.section]];
                    [arr addObject:[NSIndexPath indexPathForRow:indexPath.row+2 inSection:indexPath.section]];
                    [arr addObject:[NSIndexPath indexPathForRow:indexPath.row+1 inSection:indexPath.section]];
                }
            } else {                 // 下滑
                NSIndexPath *indexPath = [temp firstObject];
                if (indexPath.row>3) {
                    [arr addObject:[NSIndexPath indexPathForRow:indexPath.row-3 inSection:indexPath.section]];
                    [arr addObject:[NSIndexPath indexPathForRow:indexPath.row-2 inSection:indexPath.section]];
                    [arr addObject:[NSIndexPath indexPathForRow:indexPath.row-1 inSection:indexPath.section]];
                }
            }
            [needLoadArr addObjectsFromArray:arr];
        }
    }
    

    相应的,每次开始拖动的时候去清空数组。还有种情况,如果界面上有显示空白cell的时候突然手动停止滚动呢?

    - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
        [needLoadArr removeAllObjects];     // 清空数组
        // 取到当前界面上能显示的indexPaths,判断是否有隐藏
        NSArray <NSIndexPath *>*indexpaths = [_titleTableView indexPathsForVisibleRows];
        UITableViewCell *firstCell  =   [_titleTableView cellForRowAtIndexPath:indexpaths.firstObject];
        UITableViewCell *lastCell   =   [_titleTableView cellForRowAtIndexPath:indexpaths.lastObject];
        //  在当前可见的区域中,第一个cell或者最后一个cell是隐藏状态,那么重新加载可见区域内的cell
        if (firstCell.isHidden == true || lastCell.isHidden == true) {
            [_titleTableView reloadRowsAtIndexPaths:indexpaths withRowAnimation:UITableViewRowAnimationNone];
        }
    }
    也可以把判断的代码写在scrollView停止滚动监听方法里,但是个人觉得没必要,因为这种情况必定是手动触碰去停止的,这里处理没问题
    

    数据源方法:

       if (needLoadArr.count > 0) {
            if (![needLoadArr containsObject:indexPath]) {
    //            NSLog(@"该cell是快速滑动中的cell,所以隐藏");
                cell.hidden = true;
                return cell;
            }
        }
        cell.hidden = false;      // 正常显示的cell
    

    按需加载参考demo:https://github.com/johnil/VVeboTableViewDemo 这位前辈在tableView优化上做到了极致

    ⑨ 其他:
    尽量少的使用富文本;
    时间格式化对象使用同一个;

    总结:tableView性能优化的方式有很多,但不是所有的我们都需要。比如不是必需要显示的界面,预先计算行高就是浪费。我们开发者应当结合实际情况,从用户的角度出发,这是做一个优秀App最基本也是最核心的思想。

    本文章demo地址:https://github.com/qiven/ScorllContrastView

    相关文章

      网友评论

      • overla5:你好,如果我的tabview 每个cell 都有一个 进度条。怎么解决复用问题?
        第一次进来全部加载动画,然后从下一个界面 返回 改变某一行cell 的 进度,但是总有复用问题。求解
        OneKeyV:@失格人间 指点不敢当,我也是才疏学浅。cell的复用是 tableView 提高性能很好的方式,滑动时,当cell从屏幕外滚动到屏幕里时必定走一次数据源方法。建议你先初始化控件,数据请求回来再刷新 tableView,数据与cell对应上。注意控件不要重复初始化。
        overla5:@Qiven 假如10个cell,cell上都有进度条,初始化时候赋值进度。复用cell的时候也会复用进度。。按照数据源的话,在返回cell重新赋值进度,就会在cell出现的时候重新读条。。有什么办法,可以不在返回cell里面重新赋值,并且读条只会读一次,不会复用。。我是新手,描述不是太好,还望指点。
        OneKeyV:你好,不知道你出现的问题具体情况,建议从 tableView数据 和 初始化cell子视图的方式 两方面排查原因
      • Alan兰洋:很强势,就是不知道有没有数据支持确确实实被优化了... ...
        OneKeyV:用Instruments工具来查看帧率明显会有提高

      本文标题:TableView性能优化的9个常用方法

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