iOS 列表滑动的卡顿检测和优化

在上一篇文章中,我们了解了iOS的渲染循环、离屏渲染原理、卡顿原理,在这篇文章里,我们主要探索卡顿的检测如何消除卡顿

卡顿的检测

卡顿时间比

卡顿的检测,我们可以使用 instrumentAnimation Hitches来检测卡顿。

截屏2021-10-13 下午10.45.59.png

Apple的官方建议中,使用卡顿时间比来衡量卡顿的严重等级的。

截屏2021-10-12 下午8.38.38.png

如果 1s内的卡顿时间超过10ms,属于严重卡顿,需要着重解决。

Animation Hitches

接下来,我们先对我们的UITableView的滑动进行卡顿分析,在真机上运行App,查看结果:

截屏2021-10-13 下午11.05.43.png 运行结果特别差:有大量的严重卡顿,有一些卡顿达到了290ms/s

Time Profile

我们点击Time Profile一览,查看此时的函数调用栈,将系统调用筛选出去

截屏2021-10-13 下午11.08.53.png

我们找到App的主线程调用栈,查看其使用的时间占比

截屏2021-10-13 下午11.12.17.png

由分析可知:在 cellforRow方法的 model.setter方法中,存在大量的耗时操作,在 提交阶段产生了延时,导致卡顿。可以使用此方法,依次查看每处卡顿的函数调用栈,来分析提交阶段的卡顿

Show Optimization Opportunities

另一方面,我们也可以使用Xcode检测App的性能,在Xcode 12中,点击 Debug View Hierarchy,然后在 Editor选项中,默认勾选了Show Optimization Opportunities,Xcode会帮我们检测,App在运行过程中,一些优化的建议。

截屏2021-10-13 下午11.30.52.png

这段话,告诉我们,我们通过使用了 CAShapeLayer作为mask,来实现圆角效果,建议我们使用系统的 cornerRadius来实现。

为什么使用mask不行呢?

Editor选项中,选中Show Layers: 截屏2021-10-13 下午11.39.06.png

使用 CAShaperLayer + mask的形式来制作圆角,会造成离屏渲染。在cell的滚动过程中,会有 成百上千离屏渲染,提交到渲染服务器,会产生卡顿。

Color Off-screen Rendered

在 模拟器中,打开Debug -> Color Off-screen Rendered,可以检测离屏渲染,离屏渲染的视图会标记为黄色

截屏2021-10-13 下午11.53.29.png

从模拟器中,我们可以看出,容器视图造成了离屏渲染。

列表优化

圆角优化

1,在iOS13之后,可以使用系统的ApIcornerRadiuscornerCurve

view.layer.cornerRadius = 22 
view.layer.cornerCurve = .continous
复制代码

2,对于UILabel和UIButton,可以直接使用 cornerRadius

label.layer.cornerRadius = 22
复制代码

3,重写view-drawRect:的方法,使用Core Graphic绘制圆角

- (void)drawRect:(CGRect)rect {

    [super drawRect:rect];

    UIBezierPath *p = [UIBezierPath bezierPathWithRoundedRect:rect byRoundingCorners:(_bottomLeftRadius?UIRectCornerBottomLeft:0)|(_bottomRightRadius?UIRectCornerBottomRight:0)|(_topLeftRadius?UIRectCornerTopLeft:0)|(_topRightRadius?UIRectCornerTopRight:0) cornerRadii:CGSizeMake(_singleCornerRadius, 0.f)];

    CGContextRef c = UIGraphicsGetCurrentContext();

    CGContextAddPath(c, p.CGPath);

    CGContextClosePath(c);

    CGContextClip(c);

    CGContextAddRect(c, rect);

    CGContextSetFillColorWithColor(c, _bgColor.CGColor);

    CGContextFillPath(c);

}
复制代码

GPU的操作,转移到 CPU 上,均衡GPU和CPU的操作。

数据源预加载

iOS 10之后,可以使用,可以使用 UITableViewDataSourcePrefetching代理方法,可以对数据进行预加载

func tableView(tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
     let needFetch = indexPaths.contains { $0.section >= self.layoutArray.count - 1 }
        if needFetch {
            // 1.满足条件进行翻页请求
        }
    }
复制代码

数据源预处理

在网络请求完数据后,对数据进行预处理,将 NSAttributeString初始化逻辑数据源逻辑处理放在异步子线程中。

如果不使用AutoLayout,可以在数据源预处理阶段,计算好子视图的frame。

优化 cellForRow 方法

cell的背景色处理和事件绑定等事件,移至willDisplayCell中。在cellforRow方法中,不做逻辑处理,只做简单的赋值操作。

AutoLayout

如果cell采用了AutoLayout布局,应注意两个问题:

  • 1,应解决约束冲突问题,在动态操作约束值,会经常报这个错误,约束冲突时,约束engin会产生大量的CPU计算。

    解决此类问题,可以从两个方面入手:1,分析约束是否合理。 2,可以尝试改变约束的Priority

Will attempt to recover by breaking constraint

<NSLayoutConstraint:0x600003b0cc80 DBRadiusView:0x7fb9d80b0e20.bottom == UIImageView:0x7fb9d80b59c0.bottom + 8>
复制代码
  • 2,避免约束流失问题,避免在cell复用过程中反复的删除约束新增约束

避免视图删除

如果需要消息视图,避免将视图从cell中删除,尽可能的使用hidden属性。

结果对比

做完这些优化后,我们来看下卡顿的分布情况

截屏2021-10-14 上午12.45.46.png

最大的卡顿时间比32.84ms/s,在列表滑动时,没有高等级卡顿,这样就达到了一种比较理想的状态。

前后对比视频:

优化前:优酷链接

优化后:优酷链接

猜你喜欢

转载自juejin.im/post/7018601113855197198