MetricKit 新增功能
What's new in MetricKit
2020年6月24日
一句话判断
MetricKit 在 iOS 14 中新增了诊断报告(MXDiagnosticPayload),能直接给你线上用户的崩溃堆栈和 UI 挂死堆栈,相当于免费获得了一个轻量级的 APM 系统。
这场 Session 讲了什么
iOS 13 引入了 MetricKit,允许开发者收集用户设备的聚合性能指标。但当时只有 MXMetricPayload,你能知道”有 5% 的用户遇到了 hang”,却不知道 hang 在哪里。iOS 14 新增了 MXDiagnosticPayload,带来了崩溃诊断(crash diagnostics)和挂死诊断(hang diagnostics),每种诊断都包含完整的调用栈信息。
Session 详细介绍了诊断数据的类型:crashDiagnostics 提供异常类型、终止原因和线程调用栈;hangDiagnostics 提供主线程被阻塞时的完整调用栈;cpuExceptionDiagnostics 和 diskWriteExceptionDiagnostics 分别在 CPU 使用或磁盘写入超限时触发。所有诊断数据都经过匿名处理,不包含任何用户隐私信息,数据通过 Apple 服务器聚合后推送给你。
值得深挖的点
从指标到诊断的完整链路
MetricKit 现在形成了”指标 -> 诊断”的双层数据结构。MXMetricPayload 告诉你”有多少用户受到了影响”,MXDiagnosticPayload 告诉你”具体在代码的哪一行出了问题”。这个组合非常实用——你可以先在 Organizer 里看到某个版本的 hang rate 上升,然后查看对应的 hang diagnostics,直接定位到主线程被阻塞的函数调用。
Disk I/O 异常诊断
这是一个容易被忽视但非常有价值的功能。当你的 App 在后台进行了过多的磁盘写入时,系统会生成 diskWriteExceptionDiagnostics。这不仅影响性能,还可能导致 App 被系统终止。iOS 14 对后台磁盘写入有了更严格的限制,MetricKit 的这个诊断能帮你在用户投诉之前发现问题。
代码片段
注册并接收诊断数据
场景:在 App 中接收 MetricKit 的诊断报告。
import MetricKit
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// 注册 MetricKit 订阅者
MXMetricManager.shared.add(self)
return true
}
}
// 同时实现指标和诊断的接收方法
extension AppDelegate: MXMetricManagerSubscriber {
// iOS 13 就有的指标数据
func didReceive(_ payloads: [MXMetricPayload]) {
for payload in payloads {
// 处理聚合性能指标
// 包含:电池、启动时间、网络、磁盘等
}
}
// iOS 14 新增的诊断数据
func didReceive(_ payloads: [MXDiagnosticPayload]) {
for payload in payloads {
processDiagnostics(payload)
}
}
private func processDiagnostics(_ payload: MXDiagnosticPayload) {
// 崩溃诊断 - 包含完整调用栈
if let crashDiagnostics = payload.crashDiagnostics {
for crash in crashDiagnostics {
print("崩溃类型: \(crash.exceptionCode ?? "未知")")
print("终止原因: \(crash.terminationReason ?? "未知")")
// 调用栈信息可用于定位问题
}
}
// 挂死诊断 - 主线程阻塞的位置
if let hangDiagnostics = payload.hangDiagnostics {
for hang in hangDiagnostics {
print("挂死时长: \(hang.hangDuration) 秒")
}
}
}
}
解析诊断数据并上报到自己的服务器
场景:将 MetricKit 诊断数据转发到内部 APM 系统。
func didReceive(_ payloads: [MXDiagnosticPayload]) {
for payload in payloads {
// 将 payload 转换为 JSON 格式
let jsonEncoder = JSONEncoder()
if let crashData = payload.crashDiagnostics {
for crash in crashData {
do {
// MXDiagnosticPayload 支持 Codable
let data = try jsonEncoder.encode(crash)
// 上报到自己的服务器
APIService.reportCrash(data)
} catch {
print("编码崩溃数据失败: \(error)")
}
}
}
// 检查磁盘写入异常
if let diskExceptions = payload.diskWriteExceptionDiagnostics {
for exception in diskExceptions {
print("磁盘写入过量: 总写入 \(exception.totalWritesCaused)")
// 可能是某个后台任务写入了太多数据
}
}
}
}
使用自定义 Signpost 追踪关键操作
场景:标记用户登录流程的性能区间。
import os.signpost
// 在关键业务流程中使用 signpost
let log = OSLog(subsystem: "com.yourapp.performance",
category: "UserLogin")
func performLogin(username: String, password: String) {
// 标记操作开始
os_signpost(.begin, log: log, "Login started for user: %{public}s", username)
// 执行登录操作...
authenticateUser(username: username, password: password) { result in
// 标记操作结束
os_signpost(.end, log: log, "Login completed")
}
}
最佳实践
已有项目:如果只用了 MXMetricPayload,尽快加上 MXDiagnosticPayload 的处理。崩溃诊断可以直接替代部分第三方 crash reporting 工具,但建议同时保留第三方工具做交叉验证——MetricKit 的数据有延迟(通常 24-48 小时),不适合做实时告警。
新项目:在启动时立即注册 MXMetricManager。建议把指标和诊断数据都上报到自己的后端,建立长期趋势图。注意 MetricKit 的数据是聚合的、延迟的,不能替代实时 APM,但作为零成本的性能监控基础层非常有价值。
还有什么值得关注
- CPU 异常诊断(cpuExceptionDiagnostics)会在 App 持续高 CPU 使用时触发,可以帮助发现死循环或低效算法。
- MetricKit 的数据推送频率是每天一次,不是实时的,适合做趋势分析而非紧急告警。
- 所有诊断数据都支持 Codable,方便序列化存储和传输。