Highlight
iOS 13 引入了 MetricKit,允许开发者收集用户设备的聚合性能指标。但当时只有 MXMetricPayload,你能看到 hang duration histogram 里出现异常或长时间挂死,却不知道根因在哪里。iOS 14 新增了 MXDiagnosticPayload,带来了崩溃诊断(crash diagnostics)和挂死诊断(hang diagnostics),每种诊断都包含完整的调用栈信息。
核心内容
MetricKit 的定位很清楚:它补上开发者看不到真实用户设备的阶段。App 进入 TestFlight 或 App Store 后,你通常不能拿到用户手里的设备,也不能让用户打开 Instruments。MetricKit 在设备上被动收集电量和性能数据,按 24 小时聚合成 payload,再把匿名数据交给 App。
iOS 13 的 MetricKit 已经能回答”这个版本有没有变慢”。它给出启动时间、CPU 时间、内存等聚合指标,适合做 build-to-build 的趋势比较。问题在于,聚合指标只能指出症状。比如冷启动次数异常增多,或者 hang duration histogram 里出现长时间挂死,你仍然缺少根因现场。
iOS 14 的 MetricKit 2.0 扩展了两个方向。第一是新增 metrics:CPU instructions、scroll hitches、application exits。它们让开发者更准确地衡量工作量、滚动流畅度和退出原因。第二是新增 diagnostics:hang、CPU exception、disk write exception、crash。diagnostic payload 与 metric payload 同一天送达,开发者可以把趋势和调用栈放在同一条排查链路里。
这场 session 的核心价值不在于多了几个指标名。它把线上性能问题从”看到异常曲线”推进到”拿到足够定位的信息”。MetricKit 仍然是延迟的、聚合的、保护隐私的数据源,但它开始提供调用栈树、异常原因和诊断 metadata,足以支撑发布后的性能回归 triage。
详细内容
接入 MetricKit 仍然是订阅模型
(02:11)MetricKit 的基础接入流程没有改变。App 链接并导入 MetricKit,创建 MXMetricManager.shared,让自定义对象遵守 MXMetricManagerSubscriber,再实现接收 metric payload 的方法。
import MetricKit
class MySubscriber: NSObject, MXMetricManagerSubscriber {
var metricManager: MXMetricManager?
override init() {
super.init()
metricManager = MXMetricManager.shared
metricManager?.add(self)
}
override deinit() {
metricManager?.remove(self)
}
func didReceive(_ payload: [MXMetricPayload]) {
for metricPayload in payload {
// Do something with metricPayload.
}
}
}
关键点:
MXMetricManager.shared是 App 与 MetricKit 框架交互的入口。metricManager?.add(self)开始订阅系统聚合的 metric payload。metricManager?.remove(self)在对象释放时取消订阅,避免 manager 持有无效 subscriber。didReceive(_ payload: [MXMetricPayload])接收按天聚合的性能指标数组。- transcript 明确说明 payload 是匿名数据,用于保护用户隐私。
新 metrics 补足工作量、流畅度和退出原因
(05:21)MXCPUMetric 新增 CPU instructions。它统计 App 每天累计 retired instructions。CPU time 会受到时钟频率影响,CPU instructions 是硬件和频率无关的工作量指标,更适合判断一次改动是否真的让 CPU 工作变多。
(05:48)scroll hitches 面向图形性能。session 把 hitch 定义为滚动时某一帧没有在预期时间出现在屏幕上,用户看到的结果就是动画不顺。MetricKit 提供的是 App 在 UIScrollView 滚动期间发生 hitch 的时间占比。
(06:28)application exits 给出前台和后台退出原因的每日汇总。它不只统计”退出了几次”,还按原因和次数拆分。Apple 特别提到,这类数据有助于排查启动问题,以及使用后台运行框架时遇到的常见终止问题。
关键点:
- CPU instructions 用来衡量 App 在 CPU 上做了多少实际工作。
- scroll hitches 把滚动卡顿变成线上可聚合的比例指标。
- application exits 把前台和后台退出原因纳入同一天的性能 payload。
- 这些 metrics 适合和版本号、设备类型、发布批次一起存储,形成回归趋势。
diagnostics 与 metrics 走同一条管线
(08:14)iOS 14 新增的 diagnostics 不需要重写采集架构。subscriber 继续使用同一个协议,只要再实现一个接收 MXDiagnosticPayload 的方法。
func didReceive(_ payload: [MXDiagnosticPayload]) {
for diagnosticPayload in payload {
// Consume diagnosticPayload.
}
}
关键点:
- 方法签名和接收
MXMetricPayload的版本几乎一致。 - transcript 明确说 diagnostics 的语义和 metrics 基本一致。
- 系统在一天内被动收集诊断信息,最后打包成 daily diagnostic payload。
- diagnostic payload 会和同一天的 metric payload 对应,适合复用已有上报和后端处理管线。
MXDiagnosticPayload 提供可离线处理的调用栈树
(09:18)diagnostic interface 由几类基础对象组成:MXDiagnostic 是诊断基类,MXDiagnosticPayload 是一天内所有诊断的承载对象,MXCallStackTree 封装回归发生时的 backtrace。每个 diagnostic 还包含发生问题时的 App metadata,例如具体 build version。
(10:08)MXCallStackTree 里的 backtrace 是未符号化数据,设计目标是离线处理。transcript 说明 JSON 中包含 ATOS 符号化需要的信息,包括 binary UUID、offset、name 和 frame address。这个结构也会出现在 Apple 同年发布的其他性能工具里。
关键点:
MXDiagnosticPayload让 App 拿到具体诊断,而不只是聚合数值。MXCallStackTree保留 regression 发生时的调用栈树。- 调用栈默认未符号化,需要在设备外结合符号表处理。
- 后端可以把 metric payload 的趋势和 diagnostic payload 的调用栈按日期、版本和 build 关联起来。
四类诊断覆盖 hang、CPU、磁盘写入和 crash
(11:15)hang diagnostics 记录 App 长时间不响应用户输入的情况。session 明确把原因指向主线程被阻塞或繁忙,payload 提供主线程不响应的时长,以及主线程的 backtrace。
(11:35)CPU exception diagnostics 对应 Xcode Organizer 里的 energy logs。它包含高 CPU 使用期间消耗的 CPU time、总采样时间,以及消耗 CPU time 的线程 backtrace。它适合处理本地难以复现的 CPU 回归。
(11:59)disk write exception diagnostics 和 CPU exception diagnostics 很像。它记录触发异常的总写入次数,以及造成 excessive writes 的线程 backtrace。Apple 给出的触发条件是 App 突破每天 1 GB 的磁盘写入阈值。
(12:19)crash diagnostics 由 MXCrashDiagnostic 表达。每次 App crash 时,MetricKit 会提供 exception information、termination reason、bad access crash 下的 virtual memory region information,以及 backtrace。
关键点:
- hang 诊断指向主线程长时间不可响应。
- CPU exception 诊断指向高 CPU 使用期间的线程调用栈。
- disk write exception 诊断指向突破每日写入阈值的写入来源。
- crash 诊断包含异常信息、终止原因、部分内存区域信息和调用栈。
核心启发
-
做什么:给 App 做一个 MetricKit 上报层。 为什么值得做:metrics 和 diagnostics 都通过
MXMetricManagerSubscriber下发,App 端接入点很少。 怎么开始:在启动阶段创建 subscriber,分别实现didReceive(_ payload: [MXMetricPayload])和didReceive(_ payload: [MXDiagnosticPayload]),把 payload 转成后端能存储的 JSON。 -
做什么:建立发布后性能回归看板。 为什么值得做:CPU instructions、scroll hitches 和 application exits 都是按天聚合的线上指标,适合观察新版本相对旧版本的变化。 怎么开始:按 build version 存储 metric payload,先画 CPU instructions、scroll hitch ratio、foreground/background exit reasons 三组趋势。
-
做什么:把 diagnostics 变成 triage 队列。 为什么值得做:hang、CPU exception、disk write exception 和 crash 都带有可离线处理的 backtrace,比单纯的百分比指标更接近根因。 怎么开始:后端按 build version 和 diagnostic 类型分组,把调用栈树符号化后聚合,优先处理出现次数高或影响新版本的栈。
-
做什么:把线上 scroll hitch 指标和 XCTest 性能测试连起来。 为什么值得做:MetricKit 提供真实用户滚动期间的 hitch 比例,XCTest 可以在 CI 中提前测量动画回归。 怎么开始:用本场提供的 MetricKit 数据监控线上趋势,再参考 10077 为核心
UIScrollView场景补充可重复运行的性能测试。
关联 Session
- Diagnose performance issues with the Xcode Organizer — 从 Xcode Organizer 查看聚合性能数据、版本回归、磁盘写入诊断和滚动卡顿指标。
- Eliminate animation hitches with XCTest — 用 XCTest 测量滚动和动画卡顿,在问题进入线上用户设备前捕获性能回归。
- Identify trends with the Power and Performance API — 通过 App Store Connect API 把电量、性能和诊断趋势接入团队自己的 dashboard 与工作流。
评论
GitHub Issues · utterances