在上一篇文章中,我们了解了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
,在列表滑动时,没有高等级
的卡顿
,这样就达到了一种比较理想的状态。
前后对比视频:
优化前:优酷链接
优化后:优酷链接