What's new in MetricKit
Developer Tools 进阶 26m

MetricKit 新增功能

What's new in MetricKit

2020年6月24日

在 Apple 官方观看视频

一句话判断

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,方便序列化存储和传输。
WWDC 2020