思路
- iOS开发中一切与UI相关的操作都必须放在
主线程里执行,系统会以约16.7ms将UI的变化重新绘制。 - 主线程对于任务的处理是基于
Runloop机制, 它对外有如下所示:
如何设计
- 我们监听
RunloopEntry、RunloopBeforWaiting(进入休眠前)、RunloopAfterWaiting(退出休眠后)、RunLoopExit(退出RunLoop)四个时机。 - 在
RunloopEntry和RunloopAfterWaiting两个时机,标记isRunning = YES,表示主线程开始处理耗时操作了。并开始计时startTime = currentTime,表示耗时操作的起始时间。 - 在
RunloopBeforWaiting和RunLoopExit两个时机,isRunning = NO则表示主线程处于休眠或退出,未处理耗时操作。 - 另外开启一个子线程,循环检测
isRunning的值。 - 当
isRunning = YES,并且计算当前时间与起始时间的时间差:diff = currentTime - startTime - 若时间差
>=50ms,则保存一次主线程堆栈,休眠50ms,继续判断isRunning = YES步骤。 - 当时间差大于阈值,则整合堆栈,进行卡顿上报。
一些细节
- 这里涉及几个问题:
- 为何连续保存
主线程堆栈 - 如何设置
卡顿阈值 - 如何
整合堆栈 - 如何
上报卡顿
- 为何连续保存
1. 为何连续保存主线程堆栈
- 连续保存是为了尽可能的抓取到卡顿堆栈
- 在考虑到性能损耗,折中选择当卡顿时间超过
50ms时,开始保存主线程堆栈。
2. 如何设置卡顿阈值
- 当卡顿耗时在800ms-1000ms时,被定义为一般卡顿。
- 其他依次如图。
3. 如何整合堆栈
- 当达到卡顿阈值时,我们从保存连续卡顿堆栈的数组中,获取距离卡顿时最近的主线程堆栈,比如卡顿耗时
900ms,我们就获取800-1000ms时间段内保存的堆栈。(即800ms、850ms、900ms、950ms、1000ms五次堆栈信息)
4. 卡顿如何上报
- 简单的一套先入库,再读取上报,成功后删除对应数据的机制,有效避免数据丢失。
疑似卡顿的采集
- 在微信的卡顿监控方案里,认为单核CPU超过
80%认为是疑似卡顿。 - 如果希望采集疑似卡顿,在子线程检测卡顿耗时的同时,也检测CPU的占用率。
符号化处理
- 上报到服务端的数据,需要通过脚本找到对应安装包dsym文件进行符号化处理。即在打包时,就需要脚本管理每个版本的dsym文件。
火焰图
- 什么是火焰图,生成火焰图的原理,请移步iOS CPU监控方案总结内有详细说明。