metrickit

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

MetricKit

MetricKit

Collect aggregated performance metrics and crash diagnostics from production devices using MetricKit. The framework delivers daily metric payloads (CPU, memory, launch time, hang rate, animation hitches, network usage) and immediate diagnostic payloads (crashes, hangs, disk-write exceptions) with full call-stack trees for triage.
使用MetricKit从生产环境设备收集聚合的性能指标和崩溃诊断数据。该框架会输出每日指标负载(CPU、内存、启动时间、卡顿率、动画掉帧、网络使用情况)和实时诊断负载(崩溃、卡顿、磁盘写入异常),附带完整调用栈树用于问题排查。

Contents

目录

Subscriber Setup

订阅者设置

Register a subscriber as early as possible — ideally in
application(_:didFinishLaunchingWithOptions:)
or
App.init
. MetricKit starts accumulating reports after the first access to
MXMetricManager.shared
.
swift
import MetricKit

final class MetricsSubscriber: NSObject, MXMetricManagerSubscriber {
    static let shared = MetricsSubscriber()

    func subscribe() {
        MXMetricManager.shared.add(self)
    }

    func unsubscribe() {
        MXMetricManager.shared.remove(self)
    }

    func didReceive(_ payloads: [MXMetricPayload]) {
        // Handle daily metrics
    }

    func didReceive(_ payloads: [MXDiagnosticPayload]) {
        // Handle diagnostics (crashes, hangs, disk writes)
    }
}
尽可能早地注册订阅者——理想位置是
application(_:didFinishLaunchingWithOptions:)
或者
App.init
。MetricKit会在首次访问
MXMetricManager.shared
后开始积累报告。
swift
import MetricKit

final class MetricsSubscriber: NSObject, MXMetricManagerSubscriber {
    static let shared = MetricsSubscriber()

    func subscribe() {
        MXMetricManager.shared.add(self)
    }

    func unsubscribe() {
        MXMetricManager.shared.remove(self)
    }

    func didReceive(_ payloads: [MXMetricPayload]) {
        // 处理每日指标
    }

    func didReceive(_ payloads: [MXDiagnosticPayload]) {
        // 处理诊断数据(崩溃、卡顿、磁盘写入异常)
    }
}

UIKit Registration

UIKit注册方式

swift
func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
    MetricsSubscriber.shared.subscribe()
    return true
}
swift
func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
    MetricsSubscriber.shared.subscribe()
    return true
}

SwiftUI Registration

SwiftUI注册方式

swift
@main
struct MyApp: App {
    init() {
        MetricsSubscriber.shared.subscribe()
    }
    var body: some Scene {
        WindowGroup { ContentView() }
    }
}
swift
@main
struct MyApp: App {
    init() {
        MetricsSubscriber.shared.subscribe()
    }
    var body: some Scene {
        WindowGroup { ContentView() }
    }
}

Receiving Metric Payloads

接收指标负载

MXMetricPayload
arrives approximately once per 24 hours containing aggregated metrics. The array may contain multiple payloads if prior deliveries were missed.
swift
func didReceive(_ payloads: [MXMetricPayload]) {
    for payload in payloads {
        let begin = payload.timeStampBegin
        let end = payload.timeStampEnd
        let version = payload.latestApplicationVersion

        // Persist raw JSON before processing
        let jsonData = payload.jsonRepresentation()
        persistPayload(jsonData, from: begin, to: end)

        processMetrics(payload)
    }
}
Availability:
MXMetricPayload
— iOS 13.0+, macOS 10.15+, visionOS 1.0+
MXMetricPayload
大约每24小时送达一次,包含聚合后的指标数据。如果之前有推送遗漏,数组中可能包含多个负载。
swift
func didReceive(_ payloads: [MXMetricPayload]) {
    for payload in payloads {
        let begin = payload.timeStampBegin
        let end = payload.timeStampEnd
        let version = payload.latestApplicationVersion

        // 处理前先持久化原始JSON
        let jsonData = payload.jsonRepresentation()
        persistPayload(jsonData, from: begin, to: end)

        processMetrics(payload)
    }
}
可用性:
MXMetricPayload
— iOS 13.0+, macOS 10.15+, visionOS 1.0+

Receiving Diagnostic Payloads

接收诊断负载

MXDiagnosticPayload
delivers crash, hang, CPU exception, disk-write, and app-launch diagnostics. On iOS 15+ and macOS 12+, diagnostics arrive immediately rather than bundled with the daily report.
swift
func didReceive(_ payloads: [MXDiagnosticPayload]) {
    for payload in payloads {
        let jsonData = payload.jsonRepresentation()
        persistPayload(jsonData)

        if let crashes = payload.crashDiagnostics {
            for crash in crashes {
                handleCrash(crash)
            }
        }
        if let hangs = payload.hangDiagnostics {
            for hang in hangs {
                handleHang(hang)
            }
        }
        if let diskWrites = payload.diskWriteExceptionDiagnostics {
            for diskWrite in diskWrites {
                handleDiskWrite(diskWrite)
            }
        }
        if let cpuExceptions = payload.cpuExceptionDiagnostics {
            for cpuException in cpuExceptions {
                handleCPUException(cpuException)
            }
        }
        if let launchDiags = payload.appLaunchDiagnostics {
            for launchDiag in launchDiags {
                handleSlowLaunch(launchDiag)
            }
        }
    }
}
Availability:
MXDiagnosticPayload
— iOS 14.0+, macOS 12.0+, visionOS 1.0+
MXDiagnosticPayload
会推送崩溃、卡顿、CPU异常、磁盘写入、应用启动相关的诊断数据。在iOS 15+和macOS 12+系统上,诊断数据会实时送达,而非打包到每日报告中。
swift
func didReceive(_ payloads: [MXDiagnosticPayload]) {
    for payload in payloads {
        let jsonData = payload.jsonRepresentation()
        persistPayload(jsonData)

        if let crashes = payload.crashDiagnostics {
            for crash in crashes {
                handleCrash(crash)
            }
        }
        if let hangs = payload.hangDiagnostics {
            for hang in hangs {
                handleHang(hang)
            }
        }
        if let diskWrites = payload.diskWriteExceptionDiagnostics {
            for diskWrite in diskWrites {
                handleDiskWrite(diskWrite)
            }
        }
        if let cpuExceptions = payload.cpuExceptionDiagnostics {
            for cpuException in cpuExceptions {
                handleCPUException(cpuException)
            }
        }
        if let launchDiags = payload.appLaunchDiagnostics {
            for launchDiag in launchDiags {
                handleSlowLaunch(launchDiag)
            }
        }
    }
}
可用性:
MXDiagnosticPayload
— iOS 14.0+, macOS 12.0+, visionOS 1.0+

Key Metrics

核心指标

Launch Time — MXAppLaunchMetric

启动时间 — MXAppLaunchMetric

swift
if let launch = payload.applicationLaunchMetrics {
    let firstDraw = launch.histogrammedTimeToFirstDraw
    let optimized = launch.histogrammedOptimizedTimeToFirstDraw
    let resume = launch.histogrammedApplicationResumeTime
    let extended = launch.histogrammedExtendedLaunch
}
swift
if let launch = payload.applicationLaunchMetrics {
    let firstDraw = launch.histogrammedTimeToFirstDraw
    let optimized = launch.histogrammedOptimizedTimeToFirstDraw
    let resume = launch.histogrammedApplicationResumeTime
    let extended = launch.histogrammedExtendedLaunch
}

Run Time — MXAppRunTimeMetric

运行时间 — MXAppRunTimeMetric

swift
if let runTime = payload.applicationTimeMetrics {
    let fg = runTime.cumulativeForegroundTime    // Measurement<UnitDuration>
    let bg = runTime.cumulativeBackgroundTime
    let bgAudio = runTime.cumulativeBackgroundAudioTime
    let bgLocation = runTime.cumulativeBackgroundLocationTime
}
swift
if let runTime = payload.applicationTimeMetrics {
    let fg = runTime.cumulativeForegroundTime    // Measurement<UnitDuration>
    let bg = runTime.cumulativeBackgroundTime
    let bgAudio = runTime.cumulativeBackgroundAudioTime
    let bgLocation = runTime.cumulativeBackgroundLocationTime
}

CPU, Memory, and Responsiveness

CPU、内存和响应性

swift
if let cpu = payload.cpuMetrics {
    let cpuTime = cpu.cumulativeCPUTime              // Measurement<UnitDuration>
}
if let memory = payload.memoryMetrics {
    let peakMemory = memory.peakMemoryUsage           // Measurement<UnitInformationStorage>
}
if let responsiveness = payload.applicationResponsivenessMetrics {
    let hangTime = responsiveness.histogrammedApplicationHangTime
}
if let animation = payload.animationMetrics {
    let scrollHitchRate = animation.scrollHitchTimeRatio  // Measurement<Unit>
}
swift
if let cpu = payload.cpuMetrics {
    let cpuTime = cpu.cumulativeCPUTime              // Measurement<UnitDuration>
}
if let memory = payload.memoryMetrics {
    let peakMemory = memory.peakMemoryUsage           // Measurement<UnitInformationStorage>
}
if let responsiveness = payload.applicationResponsivenessMetrics {
    let hangTime = responsiveness.histogrammedApplicationHangTime
}
if let animation = payload.animationMetrics {
    let scrollHitchRate = animation.scrollHitchTimeRatio  // Measurement<Unit>
}

Network and Cellular

网络和蜂窝网络

swift
if let network = payload.networkTransferMetrics {
    let wifiUp = network.cumulativeWifiUpload          // Measurement<UnitInformationStorage>
    let wifiDown = network.cumulativeWifiDownload
    let cellUp = network.cumulativeCellularUpload
    let cellDown = network.cumulativeCellularDownload
}
swift
if let network = payload.networkTransferMetrics {
    let wifiUp = network.cumulativeWifiUpload          // Measurement<UnitInformationStorage>
    let wifiDown = network.cumulativeWifiDownload
    let cellUp = network.cumulativeCellularUpload
    let cellDown = network.cumulativeCellularDownload
}

App Exit Metrics

应用退出指标

swift
if let exits = payload.applicationExitMetrics {
    let fg = exits.foregroundExitData
    let bg = exits.backgroundExitData
    // Inspect normal, abnormal, watchdog, memory, etc.
}
swift
if let exits = payload.applicationExitMetrics {
    let fg = exits.foregroundExitData
    let bg = exits.backgroundExitData
    // 可检查正常退出、异常退出、看门狗杀死、内存杀死等情况
}

Call Stack Trees

调用栈树

MXCallStackTree
is attached to each diagnostic. Use
jsonRepresentation()
to extract frame data, then symbolicate with
atos
or by uploading dSYMs to your analytics service.
See references/metrickit-patterns.md for crash/hang handling code and JSON structure details.
Availability:
MXCallStackTree
— iOS 14.0+, macOS 12.0+, visionOS 1.0+
每个诊断数据都会附带
MXCallStackTree
。使用
jsonRepresentation()
提取帧数据,然后通过
atos
或者向分析服务上传dSYM文件进行符号解析。
查看references/metrickit-patterns.md获取崩溃/卡顿处理代码和JSON结构详情。
可用性:
MXCallStackTree
— iOS 14.0+, macOS 12.0+, visionOS 1.0+

Custom Signpost Metrics

自定义Signpost指标

Use
mxSignpost
with a MetricKit log handle to capture custom performance intervals. These appear in the daily
MXMetricPayload
under
signpostMetrics
.
swift
let metricLog = MXMetricManager.makeLogHandle(category: "Networking")
See references/metrickit-patterns.md for signpost emission patterns and reading custom metrics from payloads.
搭配MetricKit日志句柄使用
mxSignpost
来捕获自定义性能区间。这些数据会出现在每日
MXMetricPayload
signpostMetrics
字段下。
swift
let metricLog = MXMetricManager.makeLogHandle(category: "Networking")
查看references/metrickit-patterns.md获取signpost上报模式和从负载中读取自定义指标的方法。

Exporting and Uploading Payloads

导出与上传负载

Both payload types provide
jsonRepresentation()
for serialization. Always persist raw JSON to disk before processing — the system delivers each payload once. Use
pastPayloads
and
pastDiagnosticPayloads
on launch to recover missed deliveries.
See references/metrickit-patterns.md for export code and past payload retrieval.
两种负载类型都提供
jsonRepresentation()
方法用于序列化。处理前务必先将原始JSON持久化到磁盘——系统仅会推送一次每个负载。启动时可以通过
pastPayloads
pastDiagnosticPayloads
恢复遗漏的推送数据。
查看references/metrickit-patterns.md获取导出代码和历史负载获取方法。

Extended Launch Measurement

扩展启动测量

Track post-first-draw setup work as part of the launch metric:
swift
let taskID = MXLaunchTaskID("com.example.app.loadDatabase")
MXMetricManager.shared.extendLaunchMeasurement(forTaskID: taskID)
await database.load()
MXMetricManager.shared.finishExtendedLaunchMeasurement(forTaskID: taskID)
Extended launch times appear under
histogrammedExtendedLaunch
in
MXAppLaunchMetric
.
可以将首帧绘制后的初始化工作纳入启动指标统计:
swift
let taskID = MXLaunchTaskID("com.example.app.loadDatabase")
MXMetricManager.shared.extendLaunchMeasurement(forTaskID: taskID)
await database.load()
MXMetricManager.shared.finishExtendedLaunchMeasurement(forTaskID: taskID)
扩展启动时间会展示在
MXAppLaunchMetric
histogrammedExtendedLaunch
字段下。

Xcode Organizer Integration

Xcode Organizer集成

Xcode Organizer shows aggregated MetricKit data across opted-in users. Use it for trend analysis alongside on-device collection routed to your own backend.
See references/metrickit-patterns.md for Organizer tab details.
Xcode Organizer会展示已授权用户的聚合MetricKit数据。你可以结合上传到自有后端的设备端数据,用它做趋势分析。
查看references/metrickit-patterns.md获取Organizer标签页详情。

Common Mistakes

常见错误

DON'T: Subscribe to MXMetricManager too late

不要:太晚订阅MXMetricManager

The system may deliver pending payloads shortly after launch. Subscribing late (e.g., in a view controller) risks missing them entirely.
swift
// WRONG — subscribing in a view controller
override func viewDidLoad() {
    super.viewDidLoad()
    MXMetricManager.shared.add(self)
}

// CORRECT — subscribe in application(_:didFinishLaunchingWithOptions:)
func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions opts: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
    MXMetricManager.shared.add(metricsSubscriber)
    return true
}
系统可能会在启动后短时间内推送待处理的负载。如果订阅太晚(比如在ViewController中订阅),可能会完全错过这些数据。
swift
// 错误 —— 在ViewController中订阅
override func viewDidLoad() {
    super.viewDidLoad()
    MXMetricManager.shared.add(self)
}

// 正确 —— 在application(_:didFinishLaunchingWithOptions:)中订阅
func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions opts: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
    MXMetricManager.shared.add(metricsSubscriber)
    return true
}

DON'T: Ignore MXDiagnosticPayload

不要:忽略MXDiagnosticPayload

Only handling
MXMetricPayload
means you miss crash, hang, and disk-write diagnostics — the most actionable data MetricKit provides.
swift
// WRONG — only implementing metric callback
func didReceive(_ payloads: [MXMetricPayload]) { /* ... */ }

// CORRECT — implement both callbacks
func didReceive(_ payloads: [MXMetricPayload]) { /* ... */ }
func didReceive(_ payloads: [MXDiagnosticPayload]) { /* ... */ }
仅处理
MXMetricPayload
意味着你会错过崩溃、卡顿和磁盘写入诊断——这些是MetricKit提供的最具可操作性的数据。
swift
// 错误 —— 仅实现指标回调
func didReceive(_ payloads: [MXMetricPayload]) { /* ... */ }

// 正确 —— 实现两个回调
func didReceive(_ payloads: [MXMetricPayload]) { /* ... */ }
func didReceive(_ payloads: [MXDiagnosticPayload]) { /* ... */ }

DON'T: Process payloads without persisting first

不要:处理负载前不先持久化

The system delivers each payload once. If your subscriber crashes during processing, the data is lost permanently.
swift
// WRONG — process inline, crash loses data
func didReceive(_ payloads: [MXDiagnosticPayload]) {
    for p in payloads {
        riskyProcessing(p)  // If this crashes, payload is gone
    }
}

// CORRECT — persist raw JSON first, then process
func didReceive(_ payloads: [MXDiagnosticPayload]) {
    for p in payloads {
        let json = p.jsonRepresentation()
        try? json.write(to: localCacheURL())   // Safe on disk
        Task.detached { self.processAsync(json) }
    }
}
系统仅会推送一次每个负载。如果你的订阅者在处理过程中崩溃,数据会永久丢失。
swift
// 错误 —— 直接处理,崩溃会丢失数据
func didReceive(_ payloads: [MXDiagnosticPayload]) {
    for p in payloads {
        riskyProcessing(p)  // 如果这里崩溃,负载就丢失了
    }
}

// 正确 —— 先持久化原始JSON,再处理
func didReceive(_ payloads: [MXDiagnosticPayload]) {
    for p in payloads {
        let json = p.jsonRepresentation()
        try? json.write(to: localCacheURL())   // 安全存储到磁盘
        Task.detached { self.processAsync(json) }
    }
}

DON'T: Do heavy work synchronously in didReceive

不要:在didReceive中同步执行重负载任务

The callback runs on an arbitrary thread. Blocking it with heavy processing or synchronous network calls delays delivery of subsequent payloads.
swift
// WRONG — synchronous upload in callback
func didReceive(_ payloads: [MXMetricPayload]) {
    for p in payloads {
        let data = p.jsonRepresentation()
        URLSession.shared.uploadTask(with: request, from: data).resume()  // sync wait
    }
}

// CORRECT — persist and dispatch async
func didReceive(_ payloads: [MXMetricPayload]) {
    for p in payloads {
        let json = p.jsonRepresentation()
        persistLocally(json)
        Task.detached(priority: .utility) {
            await self.uploadToBackend(json)
        }
    }
}
回调运行在任意线程上。如果用重处理操作或者同步网络请求阻塞该线程,会延迟后续负载的送达。
swift
// 错误 —— 回调中同步上传
func didReceive(_ payloads: [MXMetricPayload]) {
    for p in payloads {
        let data = p.jsonRepresentation()
        URLSession.shared.uploadTask(with: request, from: data).resume()  // 同步等待
    }
}

// 正确 —— 持久化后异步调度
func didReceive(_ payloads: [MXMetricPayload]) {
    for p in payloads {
        let json = p.jsonRepresentation()
        persistLocally(json)
        Task.detached(priority: .utility) {
            await self.uploadToBackend(json)
        }
    }
}

DON'T: Expect immediate data in development

不要:在开发环境期望立即获得数据

MetricKit aggregates data over 24-hour windows. Payloads do not arrive immediately after instrumenting. Use Xcode Organizer or simulated payloads for faster iteration during development.
MetricKit以24小时为窗口聚合数据。埋点完成后负载不会立即送达。开发过程中可以使用Xcode Organizer或者模拟负载来加快迭代速度。

Review Checklist

检查清单

  • MXMetricManager.shared.add(subscriber)
    called in
    application(_:didFinishLaunchingWithOptions:)
    or
    App.init
  • Subscriber conforms to
    MXMetricManagerSubscriber
    and inherits
    NSObject
  • Both
    didReceive(_: [MXMetricPayload])
    and
    didReceive(_: [MXDiagnosticPayload])
    implemented
  • Raw
    jsonRepresentation()
    persisted to disk before processing
  • Heavy processing dispatched asynchronously off the callback thread
  • MXCallStackTree
    JSON uploaded with dSYMs for symbolication
  • Custom signpost metrics limited to critical code paths
  • pastPayloads
    and
    pastDiagnosticPayloads
    checked on launch for missed deliveries
  • Extended launch tasks call both
    extendLaunchMeasurement
    and
    finishExtendedLaunchMeasurement
  • Analytics backend accepts and stores MetricKit JSON format
  • Xcode Organizer reviewed for regression trends alongside on-device data
  • MXMetricManager.shared.add(subscriber)
    application(_:didFinishLaunchingWithOptions:)
    或者
    App.init
    中调用
  • 订阅者遵循
    MXMetricManagerSubscriber
    协议并继承
    NSObject
  • 同时实现了
    didReceive(_: [MXMetricPayload])
    didReceive(_: [MXDiagnosticPayload])
  • 处理前先将原始
    jsonRepresentation()
    持久化到磁盘
  • 重处理操作在回调线程外异步调度
  • MXCallStackTree
    JSON和dSYM一起上传用于符号解析
  • 自定义signpost指标仅用于关键代码路径
  • 启动时检查
    pastPayloads
    pastDiagnosticPayloads
    恢复遗漏的推送
  • 扩展启动任务同时调用了
    extendLaunchMeasurement
    finishExtendedLaunchMeasurement
  • 分析后端支持接收并存储MetricKit JSON格式
  • 结合Xcode Organizer和设备端数据排查回归趋势

References

参考资料