简介
卡顿的界说有多种,依照卡住的程度排列的话,有这么几种,从ANR到Hitch,本文首要描述Hitch,Render Loop以及怎么发现和修正Hitch。
Hitch
hitch代表任何时候一帧比预期的晚出现在屏幕上,hitch目标首要用在Instruments和MerticKit
如图Frame4比预期的晚出现在了屏幕上
场景
APP中发生画面改变的状况首要有以下3种场景
- Srcoll
- Animation
- Transition
Render Loop
烘托环是一个继续的进程,从用户接触事情到供给信息给操作系统来剖析和展现最终成果
这个作业与设备容量高度相关,由于这个进程在设备的刷新率下发生
屏幕依照帧率刷新的时候,每一帧都会由硬件触发一个垂直信号,在这个垂直信号宣布之前每一帧需求显现的内容需求准备好,才能供给流畅的用户体会
这个进程又这能够分红3个进程
- APP处理进程
- GPU烘托进程
- 屏幕显现进程
Double Buffering
Trible Buffering
这3个进程对应如下5个阶段
-
APP
- Event Phase
- Commit Phase
-
Render Server
- Render Prepare
- Render Execute
-
Display
Event Phase
处理事情,修正状况
事情包括
- Events
- Touch
- Networking
- Keyboard
- Timers
事情中设置BackgroudColor,设置Bounds等,在设置好之后,setNeedsLayout会进行状况的修正和传递
Commit Phase
提交阶段布局和制作视图布局和状况
Render Prepare Phase
处理layers图层,作用,和用于履行的动画
Render Execute
运用GPU把图层和作用制作出来
并行处理联系
Hitch分类
Commit Hitch
APP提交和处理事情的时刻过长
延迟1帧,即16.67ms
延迟2帧,即33.34ms
Render Hitch
烘托无法及时完成
延迟1帧,即16.67ms
Hitch Time Measurement
全体hitch的时刻
- 不同的进程,设备刷新率和许多正常的帧
- 不好比较
Hitch Time Ratio
全体hitch的时刻跟全体总计时刻的份额
-
为不同的设备统一了总共hitch的时刻
以这30帧为例,由于hitch time是0,所以0ms/0.5s = 0 ms/s
假如其中hitch time的时刻是100.02ms,那么计算下来便是200.04ms/s
功能目标
- 严重是大于等于10ms/s
- 正告是5ms/s与10ms/s之间
- 很好是小于5ms/s
修正Hitch
Commit Phase
Commit Transaction
应用的视图结构从等候事情的状况转化到接纳事情的状况
从接纳事情的状况到提交的状况
Layout
每个需求layout的View的layoutSubviews()
办法都会被调用
Layout needed的条件
- 移动位置(frame,bounds,transform)
- 添加或许移除视图
- 显式调用
setNeedsLayout()
Display
每个需求更新内容的View的draw(rect:)
办法都会被调用
Display needed的条件
- 添加的view覆盖了
draw(rect:)
- 显现调用
setNeedsDisplay()
Prepare
- 假如有必要的图片解码
- 假如有必要的图片转化
Commit
提交会做
- 递归打包视图树
- 发送到烘托服务者
Find hitches with Instruments
在Instruments的东西中能够找到Animation Hitches东西
左侧列出了Events,Commits,Renders,GPU,Frame Lifetime和Display信息
正常的状况
Acceptable Latency
Hitch Duration
在下边的数据追踪详情中能够看到Acceptable Latency数据,Hitch Duration数据和Hitch Type类型数据
能够挑选和筛选到对应Commit Phase,经过检查Thread中的调用栈来找到导致Hitch发生的原因
实例中是在复用函数事情prepareForReuse()
中遍历调用了addSubview()
和removeFromSuperview()
的办法导致Hitch
Recommendations
坚持视图轻量级
- 运用CALayer的特点好于自界说
draw(rect:)
代码 - 没必要的状况下,不要覆写
draw(rect:)
- 复用视图防止添加和移除的操作
- 能够运用
hidden
特点
削减大开支或许重复的布局
- 尽量运用setNeedsLayout(), 不必layoutIfNeeded()
- 尽量运用简明的束缚
- 递归布局的开支很大
Render Phase
Render Phase
Render Server
Render Phase分为两个细分阶段
- Render Prepare
- Render Execute
Render Prepare
把视图层级和作用拆解成一步一步的简单操作
Render Execute
一步一步进行纹理叠加
遇到Shadow的时候,需求拓荒新的烘托空间,离屏烘托发生了
另一块空间,离屏空间烘托完成后,再复制回原烘托空间中,进行纹理叠加
离屏烘托
任何时候当GPU必须拓荒另外一个区域烘托视图,然后再复制回来
几个首要原因
- Shadowing
- Masking
- Rounded Rectangles
- Visual Effects
Shadowing
Masking
Rounded Rectangles
Visual Effects
Find hitches with Instruments
能够观察到GPU Phase超时
经过Xcode中Debug Navigator检查视图层级
Layer的描述中标记了离屏烘托的状况
Editor中Show Optimization Opportunities
Xcode中的Issue Navigator会显现出视图对应的问题
Recommendations
尽量运用供给的API
- 设置
shadowPath
特点界说暗影形状 - 运用
cornerRadius
和cornerCurve
来画圆角
优化mask讳饰层
- 尽量运用maskToBounds而不是自界说mask
- 假如内容不超过边界不要敞开maskToBounds
结合XCTest
iOS14开始,在开发,Beta和出产阶段都能够运用东西套件来追踪hitch,本节结合XCTest从开发阶段排查hitch问题
- XCTest framework供给了在单元测试和UI测试中直接纳集hitch和animation数据
- MetricKit和Xcode Organizer能够从用户侧搜集功能数据
Xcode11开始,引荐运用XCTMetrics
XCTApplicationLaunchMetric
XCTCPUMetric
XCTClockMetric
XCTMemoryMetric
XCTOSSignpostMetric
XCTStorageMetric
Xcode11中,咱们能够运用XCTOSSignpostMetric
来衡量os_signpost的距离
Xcode12中,咱们运用animation os_signpost
距离,不只能够获得时长信息,还能够获得3个hitch相关信息,帧率和帧数
需求搜集这些目标,有3种办法在代码中触发一个os_signpost
距离,分红非动画距离和动画距离2种类型
Xcode11中需求运用begin和end来检测非动画距离,在Xcode12中只需求换成运用animationBegin接口即可
os_signpost(.animationBegin, log: logHandle, name: "performAnimationInterval")
os_signpost(.end, log: logHandle, name: "performAnimationInterval")
除了自界说距离,你也能够运用关于navigation转场和滑动的预界说的UIKit的检测距离
extension XCTOSSignpostMetric {
open class var navigationTransitionMetric: XCTMetric { get }
open class var customNavigationTransitionMetric: XCTMetric { get }
open class var scrollDecelerationMetric: XCTMetric { get }
open class var scrollDraggingMetric: XCTMetric { get }
}
如示例中,点击cell后进入collectionView并滑动,计划检测scrollDeceleration子目标。丈量代码块,默许会检测5次来搜集功能数据。
// Measure scrolling animation performance using a Performance XCTest
func testScrollingAnimationPerformance() throws {
app.launch()
app.staticTexts["Meal Planner"].tap()
let foodCollection = app.collectionViews.firstMatch
measure(metrics: [XCTOSSignpostMetric.scrollDecelerationMetric]) {
foodCollection.swipeUp(velocity: .fast)
}
}
为了防止上滑5次显现5次不同内容,能够每次在运转前重置应用的状况,咱们能够运用XCTMeasureOptions
来让咱们的检测代码块知道咱们会手动中止检测搜集,把参数传递到丈量代码块中
func testScrollingAnimationPerformance() throws {
app.launch()
app.staticTexts["Meal Planner"].tap()
let foodCollection = app.collectionViews.firstMatch
let measureOptions = XCTMeasureOptions()
measureOptions.invocationOptions = [.manuallyStop]
measure(metrics: [XCTOSSignpostMetric.scrollDecelerationMetric],
options: measureOptions) {
foodCollection.swipeUp(velocity: .fast)
stopMeasuring()
foodCollection.swipeDown(velocity: .fast)
}
}
但是在运转丈量代码之前,咱们需求在test scheme中修正一些装备消除对功能检测的影响
- 挑选release build configuration
- 封闭the debugger
- 封闭automatic screenshot
- 封闭code coverage
- 封闭所有diagnostic options
跑完测试能够得到以下数据,挑选到Hitch Ratio Rate目标数据
5次丈量数据
平均值1.2ms/s
能够把这个数据设置为基准数据,用于跟后续的功能数据做比照
检查Number of hitches,承认此刻无hitches
添加了图片加载等代码后,再检查Number of hitches,发现有hitches发生
问题出现在scaleAspectFit办法的调用中,主线程在负责烘托UI剩余部分的时候,该办法中又在重新制作图片
能够经过设置Core Animation供给的setContentMode
来处理图片的重绘,运用现有的图片像素,削减主线程作业量
再启动XCTest发现问题免除
引证
Explore UI animation hitches and the render loop
Find and fix hitches in the commit phase
Demystify and eliminate hitches in the render phase
Eliminate animation hitches with XCTest
Apple Document XCTMetric