iOS 性能优化:使用 MetricKit 2.0 收集数据
作者:Jerry4me,iOS 开发者,目前就职于字节跳动抖音直播团
Sessions: https://developer.apple.com/videos/play/wwdc2020/10081/
概览
MetricKit 是苹果 iOS13 推出的框架,他会在一天结束后,将过去 24 小时内收集的性能数据归集在一起,并在下一次 App 启动时,通过 delegate 方法回调给我们。
@protocol MXMetricManagerSubscriber <NSObject>
- (void)didReceiveMetricPayloads:(NSArray<MXMetricPayload *> * _Nonnull)payloads;
@end
一个 MXMetricPayload
对象就是一个周期(24 小时)内收集到的所有性能指标的集合。如果有 24 小时以前未被收集过的数据,也会在这里一并返回给我们。所以 delegate 方法这里给到我们的是一个数组。想了解关于 MetricKit 指标的更详细的信息,可阅读 WWDC 2019 Session - 417 Improving Battery Life and Performance[1].
MetricKit 将系统收集的数据交给开发者,让我们自己去决定如何利用这些数据去打造一个更省电、性能更好的 App。
如何集成使用 MetricKit
MetricKit 的接入基本属于无侵入,并且步骤十分简单。
获取
MXMetricManager
单例添加订阅者
实现 delegate 回调
That's it !
系统是怎么收集 MetricKit 的数据的
系统会在一天你使用 App 的时候被动地获取、汇总 App 的性能数据。而且为了保护用户的隐私,这些数据都是匿名存放的。最终一天结束的时候,系统会将一天内收集到的这些数据打包成一个 MetricKit 的数据包,也就是前面提到的 MXMetricPayload
。
MXMetricPayload
包含了许多性能指标,如下:
@interface MXMetricPayload : NSObject <NSSecureCoding>
/// 电池指标
@property (readonly, strong, nullable) MXCellularConditionMetric *cellularConditionMetrics;
@property (readonly, strong, nullable) MXCPUMetric *cpuMetrics;
@property (readonly, strong, nullable) MXDisplayMetric *displayMetrics;
@property (readonly, strong, nullable) MXGPUMetric *gpuMetrics;
@property (readonly, strong, nullable) MXLocationActivityMetric *locationActivityMetrics;
@property (readonly, strong, nullable) MXNetworkTransferMetric *networkTransferMetrics;
/// 性能指标
@property (readonly, strong, nullable) MXAppExitMetric *applicationExitMetrics; // New for iOS 14
@property (readonly, strong, nullable) MXAppRunTimeMetric *applicationTimeMetrics;
@property (readonly, strong, nullable) MXMemoryMetric *memoryMetrics;
/// 用户交互相关的响应性指标
@property (readonly, strong, nullable) MXAppLaunchMetric *applicationLaunchMetrics;
@property (readonly, strong, nullable) MXAnimationMetric *animationMetrics; // New for iOS 14
@property (readonly, strong, nullable) MXAppResponsivenessMetric *applicationResponsivenessMetrics;
/// 磁盘存取指标
@property (readonly, strong, nullable) MXDiskIOMetric *diskIOMetrics;
/// 自定义指标
@property (readonly, strong, nullable) NSArray<MXSignpostMetric *> *signpostMetrics;
@end
系统会将打包的这些数据转化成可读性更高的格式输出给我们,如下图所示:
数据分为三种聚合类型:累积的总和、平均数和分组数据。系统这样处理后,对于我们后续要针对某指标进行的一系列性能优化具备一定的指导意义。例如我们需要把某某指标的总耗时、平均耗时要下降到多少毫秒等等。
MetricKit 1.0 未解决的问题
在某些性能指标方面,光有这些还不足以让我们去定位问题,找到解决问题的方案。
例如 App 的冷启动和热启动的时长数据,我们可以看到数据中冷启的次数远远比热启的次数要多。为了提高用户体验,我们当然是希望热启的次数更多。但是这种情况我们没办法根据这些数据去帮助我们定位和解决问题。
另一个典型的指标是 CPU 时间。我们可以看到 App 在前台的时间要远多于 CPU 时间。单纯根据这份报表我们没办法知道这是否是我们预期内的结果。
不用着急,MetricKit 2.0 给了我们新的指标和 API 帮助我们深入地研究这类问题,进一步明确 App 的工作负荷、性能和稳定性。
MetricKit 2.0
新的性能指标
MetricKit 2.0 新增了三种指标:CPU 指令、滚动卡顿和应用程序退出。
@property (readonly, strong) NSMeasurement<NSUnit *> *cumulativeCPUInstructions; // New for iOS 14
CPU 指令是 MXCPUMetric
新增的一个属性 cumulativeCPUInstructions
表示一个周期内 CPU 执行的指令总数。这个指标是衡量 App 在 CPU 工作负荷的绝对指标,与硬件和频率都没有关系。他能帮助我们更准确地量化 App 的总负荷。
@property (readonly, strong) MXAnimationMetric *animationMetrics; // New for iOS 14
滚动卡顿是 MetricKit 在 iOS 14 上新增的一个指标 MXAnimationMetric
。他指的是渲染的帧在滚动过程中的预期时间未出现在屏幕上,也就是我们俗称的掉帧。MetricKit 会为我们提供 App 中滚动卡顿的时间与滚动的时间之比来帮我们了解 App 的滚动性能。
如果想要了解怎么用 XCTest 对这个指标进行衡量,可以观看 WWDC 2020 - 10077 Eliminate animation hitches with XCTest[2].
@property (readonly, strong) MXAppExitMetric *applicationExitMetrics;
应用程序退出是 MetricKit 在 iOS 14 上新增的一个指标 MXAppExitMetric
。他统计的是每天应用程序在前台、后台运行的时候退出或被杀的原因概述。
如果想要了解怎么利用这指标,可以观看 WWDC 2020 - 10078 Why is my app getting killed?[3]
新特性 - 诊断
诊断功能相关 API
MetricKit 2.0 为我们提供了 4 种诊断类型:应用程序挂起、崩溃、磁盘写入异常和 CPU 异常。
挂起是指应用程序长时间不响应用户输入的情况,可能是主线程繁忙或被阻塞。MetricKit 为我们提供应用程序无响应的持续时间以及主线程的调用栈。
CPU 异常诊断可以在 Xcode Organizer 中查看,诊断信息包含消耗的 CPU 时间、CPU 高使用率期间的总时长和消耗 CPU 时间的线程调用栈。与性能数据(Metric Payload)结合起来对于找出不易复现的回归非常有用。
磁盘写入异常诊断跟 CPU 异常诊断很相似,每个诊断会包含造成异常的写入总数,以及导致过多写入的线程调用栈。并且这些诊断当应用程序超过了每天 1GB 的阈值时就会生成。
崩溃诊断会把每次 App 崩溃的时候将异常信息、崩溃原因、错误访问情况下的虚拟内存信息以及崩溃调用栈给到我们。
如何使用诊断功能
MetricKit 2.0 为我们提供了新的 delegate 回调,让我们可以方便地查看诊断信息。
集成步骤也非常简单,实现 delegate 协议的方法即可。
MetricKit 会在用户使用 App 的时候同步收集诊断信息,然后每天结束的时候将他们打包成 MXDiagnosticPayload
给到我们。这样我们就拥有了同一个时间段的性能数据和诊断数据。
由于他们是一一对应的,所以我们在对性能数据产生疑问的时候就可以掏出对应的诊断数据来进行排查。由于有了这些一一的对应关系,所以 MetricKit 2.0 也随之来了一些新的基类:
MXDiagnostic
:所有诊断类集成的基类MXDiagnosticPayload
:诊断包,包含一天结束时的所有诊断MXCallStackTree
:新数据类,用于封装当前环境的调用栈
MXCallStackTree
封装的调用栈并没有经过符号化,旨在用于设备外处理,非 debug 用。转换成 JSON 后如下所示:
如果想要了解怎么利用这些调用栈数据,可以观看 WWDC 2020 - 10057 Identify trends with the Power and Performance API[4].
总结
总的来说,MetricKit 2.0 填了 1.0 版本的一些坑,为我们提供了更多强大的 API 以及配套的工具,帮助我们去分析和解决一些疑难杂症。只要把 Metric Payload 与 Diagnostic Payload 结合起来,双剑合璧,对我们去定位线上的一些难以复现、毫无头绪的问题有很大的帮助。
推荐阅读
✨ iOS 持续集成:更完备的 App Store Connect API
关注我们
我们是「老司机技术周报」,每周会发布一份关于 iOS 的周报,也会定期分享一些和 iOS 相关的技术。欢迎关注。
关注有礼,关注【老司机技术周报】,回复「2020」,领取学习大礼包。
支持作者
这篇文章的内容来自于 《WWDC20 内参》。在这里给大家推荐一下这个专栏,专栏目前已经创作了 108 篇文章,只需要 29.9 元。点击【阅读原文】,就可以购买继续阅读 ~
WWDC 内参 系列是由老司机周报、知识小集合以及 SwiftGG 几个技术组织发起的。已经做了几年了,口碑一直不错。 主要是针对每年的 WWDC 的内容,做一次精选,并号召一群一线互联网的 iOS 开发者,结合自己的实际开发经验、苹果文档和视频内容做二次创作。
参考资料
WWDC 2019 Session - 417 Improving Battery Life and Performance: https://developer.apple.com/videos/play/wwdc2019/417/
[2]WWDC 2020 - 10077 Eliminate animation hitches with XCTest: https://developer.apple.com/videos/play/wwdc2020/10077
[3]WWDC 2020 - 10078 Why is my app getting killed?: https://developer.apple.com/videos/play/wwdc2020/10078
[4]WWDC 2020 - 10057 Identify trends with the Power and Performance API: https://developer.apple.com/videos/play/wwdc2020/10057