在上一篇文章中,我们了解了iOS的渲染循环、离屏渲染原理、卡顿原理,在这篇文章里,我们主要探索卡顿的检测和如何消除卡顿。
卡顿的检测
卡顿时间比
卡顿的检测,我们可以使用 instrument的Animation Hitches来检测卡顿。
在Apple的官方建议中,使用卡顿时间比来衡量卡顿的严重等级的。
如果 1s内的卡顿时间超过10ms,属于严重卡顿,需要着重解决。
Animation Hitches
接下来,我们先对我们的UITableView的滑动进行卡顿分析,在真机上运行App,查看结果:
运行结果特别差:有大量的
严重卡顿,有一些卡顿达到了290ms/s。
Time Profile
我们点击Time Profile一览,查看此时的函数调用栈,将系统调用筛选出去
我们找到App的主线程调用栈,查看其使用的时间占比
由分析可知:在 cellforRow方法的 model.setter方法中,存在大量的耗时操作,在 提交阶段产生了延时,导致卡顿。可以使用此方法,依次查看每处卡顿的函数调用栈,来分析提交阶段的卡顿。
Show Optimization Opportunities
另一方面,我们也可以使用Xcode检测App的性能,在Xcode 12中,点击 Debug View Hierarchy,然后在 Editor选项中,默认勾选了Show Optimization Opportunities,Xcode会帮我们检测,App在运行过程中,一些优化的建议。
这段话,告诉我们,我们通过使用了 CAShapeLayer作为mask,来实现圆角效果,建议我们使用系统的 cornerRadius来实现。
为什么使用mask不行呢?
在Editor选项中,选中Show Layers:
使用 CAShaperLayer + mask的形式来制作圆角,会造成离屏渲染。在cell的滚动过程中,会有 成百上千个离屏渲染,提交到渲染服务器,会产生卡顿。
Color Off-screen Rendered
在 模拟器中,打开Debug -> Color Off-screen Rendered,可以检测离屏渲染,离屏渲染的视图会标记为黄色。
从模拟器中,我们可以看出,容器视图造成了离屏渲染。
列表优化
圆角优化
1,在iOS13之后,可以使用系统的ApIcornerRadius和cornerCurve。
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属性。
结果对比
做完这些优化后,我们来看下卡顿的分布情况
最大的卡顿时间比为32.84ms/s,在列表滑动时,没有高等级的卡顿,这样就达到了一种比较理想的状态。
前后对比视频:
优化前:优酷链接
优化后:优酷链接