axiom-metrickit-ref

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

MetricKit API Reference

MetricKit API 参考文档

Complete API reference for collecting field performance metrics and diagnostics using MetricKit.
使用MetricKit收集现场性能指标与诊断数据的完整API参考。

Overview

概述

MetricKit provides aggregated, on-device performance and diagnostic data from users who opt into sharing analytics. Data is delivered daily (or on-demand in development).
MetricKit 提供来自选择共享分析数据的用户的聚合式设备端性能与诊断数据。数据每日推送(开发环境下可按需获取)。

When to Use This Reference

何时使用本参考文档

Use this reference when:
  • Setting up MetricKit subscriber in your app
  • Parsing MXMetricPayload or MXDiagnosticPayload
  • Symbolicating MXCallStackTree crash data
  • Understanding background exit reasons (jetsam, watchdog)
  • Integrating MetricKit with existing crash reporters
For hang diagnosis workflows, see
axiom-hang-diagnostics
. For general profiling with Instruments, see
axiom-performance-profiling
. For memory debugging including jetsam, see
axiom-memory-debugging
.
在以下场景中使用本参考:
  • 在应用中设置MetricKit订阅者
  • 解析MXMetricPayload或MXDiagnosticPayload
  • 对MXCallStackTree崩溃数据进行符号化
  • 理解后台退出原因(jetsam、看门狗)
  • 将MetricKit与现有崩溃报告工具集成
有关挂起诊断工作流,请查看
axiom-hang-diagnostics
。 有关使用Instruments进行常规性能分析,请查看
axiom-performance-profiling
。 有关包括jetsam在内的内存调试,请查看
axiom-memory-debugging

Common Gotchas

常见注意事项

  1. 24-hour delay — MetricKit data arrives once daily; it's not real-time debugging
  2. Call stacks require symbolication — MXCallStackTree frames are unsymbolicated; keep dSYMs
  3. Opt-in only — Only users who enable "Share with App Developers" contribute data
  4. Aggregated, not individual — You get counts and averages, not per-user traces
  5. Simulator doesn't work — MetricKit only collects on physical devices
iOS Version Support:
FeatureiOS Version
Basic metrics (battery, CPU, memory)iOS 13+
Diagnostic payloadsiOS 14+
Hang diagnosticsiOS 14+
Launch diagnosticsiOS 16+
Immediate delivery in deviOS 15+
  1. 24小时延迟 — MetricKit数据每日推送一次;不支持实时调试
  2. 调用栈需要符号化 — MXCallStackTree的栈帧未经过符号化;请保留dSYM文件
  3. 仅选择加入的用户 — 只有启用了“与应用开发者共享”的用户才会贡献数据
  4. 聚合数据而非个体数据 — 你将获得统计数量与平均值,而非单个用户的追踪数据
  5. 模拟器不支持 — MetricKit仅在物理设备上收集数据
iOS版本支持:
功能iOS版本
基础指标(电池、CPU、内存)iOS 13+
诊断负载iOS 14+
挂起诊断iOS 14+
启动诊断iOS 16+
开发环境下即时推送iOS 15+

Part 1: Setup

第一部分:设置

Basic Integration

基础集成

swift
import MetricKit

class AppMetricsSubscriber: NSObject, MXMetricManagerSubscriber {

    override init() {
        super.init()
        MXMetricManager.shared.add(self)
    }

    deinit {
        MXMetricManager.shared.remove(self)
    }

    // MARK: - MXMetricManagerSubscriber

    func didReceive(_ payloads: [MXMetricPayload]) {
        for payload in payloads {
            processMetrics(payload)
        }
    }

    func didReceive(_ payloads: [MXDiagnosticPayload]) {
        for payload in payloads {
            processDiagnostics(payload)
        }
    }
}
swift
import MetricKit

class AppMetricsSubscriber: NSObject, MXMetricManagerSubscriber {

    override init() {
        super.init()
        MXMetricManager.shared.add(self)
    }

    deinit {
        MXMetricManager.shared.remove(self)
    }

    // MARK: - MXMetricManagerSubscriber

    func didReceive(_ payloads: [MXMetricPayload]) {
        for payload in payloads {
            processMetrics(payload)
        }
    }

    func didReceive(_ payloads: [MXDiagnosticPayload]) {
        for payload in payloads {
            processDiagnostics(payload)
        }
    }
}

Registration Timing

注册时机

Register subscriber early in app lifecycle:
swift
@main
struct MyApp: App {
    @StateObject private var metricsSubscriber = AppMetricsSubscriber()

    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}
Or in AppDelegate:
swift
func application(_ application: UIApplication,
                 didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    metricsSubscriber = AppMetricsSubscriber()
    return true
}
在应用生命周期早期注册订阅者:
swift
@main
struct MyApp: App {
    @StateObject private var metricsSubscriber = AppMetricsSubscriber()

    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}
或在AppDelegate中:
swift
func application(_ application: UIApplication,
                 didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    metricsSubscriber = AppMetricsSubscriber()
    return true
}

Development Testing

开发测试

In iOS 15+, trigger immediate delivery via Debug menu:
Xcode > Debug > Simulate MetricKit Payloads
Or programmatically (debug builds only):
swift
#if DEBUG
// Payloads delivered immediately in development
// No special code needed - just run and wait
#endif
在iOS 15及以上版本中,可通过Debug菜单触发即时推送:
Xcode > Debug > Simulate MetricKit Payloads
或通过代码实现(仅调试构建):
swift
#if DEBUG
// 开发环境下负载会即时推送
// 无需特殊代码 - 只需运行等待即可
#endif

Part 2: MXMetricPayload

第二部分:MXMetricPayload

MXMetricPayload
contains aggregated performance metrics from the past 24 hours.
MXMetricPayload
包含过去24小时的聚合性能指标。

Payload Structure

负载结构

swift
func processMetrics(_ payload: MXMetricPayload) {
    // Time range for this payload
    let start = payload.timeStampBegin
    let end = payload.timeStampEnd

    // App version that generated this data
    let version = payload.metaData?.applicationBuildVersion

    // Access specific metric categories
    if let cpuMetrics = payload.cpuMetrics {
        processCPU(cpuMetrics)
    }

    if let memoryMetrics = payload.memoryMetrics {
        processMemory(memoryMetrics)
    }

    if let launchMetrics = payload.applicationLaunchMetrics {
        processLaunches(launchMetrics)
    }

    // ... other categories
}
swift
func processMetrics(_ payload: MXMetricPayload) {
    // 该负载的时间范围
    let start = payload.timeStampBegin
    let end = payload.timeStampEnd

    // 生成此数据的应用版本
    let version = payload.metaData?.applicationBuildVersion

    // 访问特定指标类别
    if let cpuMetrics = payload.cpuMetrics {
        processCPU(cpuMetrics)
    }

    if let memoryMetrics = payload.memoryMetrics {
        processMemory(memoryMetrics)
    }

    if let launchMetrics = payload.applicationLaunchMetrics {
        processLaunches(launchMetrics)
    }

    // ... 其他类别
}

CPU Metrics (MXCPUMetric)

CPU指标(MXCPUMetric)

swift
func processCPU(_ metrics: MXCPUMetric) {
    // Cumulative CPU time
    let cpuTime = metrics.cumulativeCPUTime  // Measurement<UnitDuration>

    // iOS 14+: CPU instruction count
    if #available(iOS 14.0, *) {
        let instructions = metrics.cumulativeCPUInstructions  // Measurement<Unit>
    }
}
swift
func processCPU(_ metrics: MXCPUMetric) {
    // 累计CPU时间
    let cpuTime = metrics.cumulativeCPUTime  // Measurement<UnitDuration>

    // iOS 14及以上:CPU指令计数
    if #available(iOS 14.0, *) {
        let instructions = metrics.cumulativeCPUInstructions  // Measurement<Unit>
    }
}

Memory Metrics (MXMemoryMetric)

内存指标(MXMemoryMetric)

swift
func processMemory(_ metrics: MXMemoryMetric) {
    // Peak memory usage
    let peakMemory = metrics.peakMemoryUsage  // Measurement<UnitInformationStorage>

    // Average suspended memory
    let avgSuspended = metrics.averageSuspendedMemory  // MXAverage<UnitInformationStorage>
}
swift
func processMemory(_ metrics: MXMemoryMetric) {
    // 峰值内存使用量
    let peakMemory = metrics.peakMemoryUsage  // Measurement<UnitInformationStorage>

    // 平均挂起内存
    let avgSuspended = metrics.averageSuspendedMemory  // MXAverage<UnitInformationStorage>
}

Launch Metrics (MXAppLaunchMetric)

启动指标(MXAppLaunchMetric)

swift
func processLaunches(_ metrics: MXAppLaunchMetric) {
    // First draw (cold launch) histogram
    let firstDrawHistogram = metrics.histogrammedTimeToFirstDraw

    // Resume time histogram
    let resumeHistogram = metrics.histogrammedApplicationResumeTime

    // Optimized time to first draw (iOS 15.2+)
    if #available(iOS 15.2, *) {
        let optimizedLaunch = metrics.histogrammedOptimizedTimeToFirstDraw
    }

    // Parse histogram buckets
    for bucket in firstDrawHistogram.bucketEnumerator {
        if let bucket = bucket as? MXHistogramBucket<UnitDuration> {
            let start = bucket.bucketStart  // e.g., 0ms
            let end = bucket.bucketEnd      // e.g., 100ms
            let count = bucket.bucketCount  // Number of launches in this range
        }
    }
}
swift
func processLaunches(_ metrics: MXAppLaunchMetric) {
    // 首次绘制(冷启动)直方图
    let firstDrawHistogram = metrics.histogrammedTimeToFirstDraw

    // 恢复时间直方图
    let resumeHistogram = metrics.histogrammedApplicationResumeTime

    // 优化后的首次绘制时间(iOS 15.2+)
    if #available(iOS 15.2, *) {
        let optimizedLaunch = metrics.histogrammedOptimizedTimeToFirstDraw
    }

    // 解析直方图桶
    for bucket in firstDrawHistogram.bucketEnumerator {
        if let bucket = bucket as? MXHistogramBucket<UnitDuration> {
            let start = bucket.bucketStart  // 例如:0ms
            let end = bucket.bucketEnd      // 例如:100ms
            let count = bucket.bucketCount  // 此范围内的启动次数
        }
    }
}

Application Exit Metrics (MXAppExitMetric) — iOS 14+

应用退出指标(MXAppExitMetric)—— iOS 14+

swift
@available(iOS 14.0, *)
func processExits(_ metrics: MXAppExitMetric) {
    let fg = metrics.foregroundExitData
    let bg = metrics.backgroundExitData

    // Foreground (onscreen) exits
    let fgNormal = fg.cumulativeNormalAppExitCount
    let fgWatchdog = fg.cumulativeAppWatchdogExitCount
    let fgMemoryLimit = fg.cumulativeMemoryResourceLimitExitCount
    let fgMemoryPressure = fg.cumulativeMemoryPressureExitCount
    let fgBadAccess = fg.cumulativeBadAccessExitCount
    let fgIllegalInstruction = fg.cumulativeIllegalInstructionExitCount
    let fgAbnormal = fg.cumulativeAbnormalExitCount

    // Background exits
    let bgSuspended = bg.cumulativeSuspendedWithLockedFileExitCount
    let bgTaskTimeout = bg.cumulativeBackgroundTaskAssertionTimeoutExitCount
    let bgCPULimit = bg.cumulativeCPUResourceLimitExitCount
}
swift
@available(iOS 14.0, *)
func processExits(_ metrics: MXAppExitMetric) {
    let fg = metrics.foregroundExitData
    let bg = metrics.backgroundExitData

    // 前台(屏幕显示中)退出
    let fgNormal = fg.cumulativeNormalAppExitCount
    let fgWatchdog = fg.cumulativeAppWatchdogExitCount
    let fgMemoryLimit = fg.cumulativeMemoryResourceLimitExitCount
    let fgMemoryPressure = fg.cumulativeMemoryPressureExitCount
    let fgBadAccess = fg.cumulativeBadAccessExitCount
    let fgIllegalInstruction = fg.cumulativeIllegalInstructionExitCount
    let fgAbnormal = fg.cumulativeAbnormalExitCount

    // 后台退出
    let bgSuspended = bg.cumulativeSuspendedWithLockedFileExitCount
    let bgTaskTimeout = bg.cumulativeBackgroundTaskAssertionTimeoutExitCount
    let bgCPULimit = bg.cumulativeCPUResourceLimitExitCount
}

Scroll Hitch Metrics (MXAnimationMetric) — iOS 14+

滚动卡顿指标(MXAnimationMetric)—— iOS 14+

swift
@available(iOS 14.0, *)
func processHitches(_ metrics: MXAnimationMetric) {
    // Scroll hitch rate (hitches per scroll)
    let scrollHitchRate = metrics.scrollHitchTimeRatio  // Double (0.0 - 1.0)
}
swift
@available(iOS 14.0, *)
func processHitches(_ metrics: MXAnimationMetric) {
    // 滚动卡顿率(每次滚动的卡顿次数)
    let scrollHitchRate = metrics.scrollHitchTimeRatio  // Double (0.0 - 1.0)
}

Disk I/O Metrics (MXDiskIOMetric)

磁盘I/O指标(MXDiskIOMetric)

swift
func processDiskIO(_ metrics: MXDiskIOMetric) {
    let logicalWrites = metrics.cumulativeLogicalWrites  // Measurement<UnitInformationStorage>
}
swift
func processDiskIO(_ metrics: MXDiskIOMetric) {
    let logicalWrites = metrics.cumulativeLogicalWrites  // Measurement<UnitInformationStorage>
}

Network Metrics (MXNetworkTransferMetric)

网络指标(MXNetworkTransferMetric)

swift
func processNetwork(_ metrics: MXNetworkTransferMetric) {
    let cellUpload = metrics.cumulativeCellularUpload
    let cellDownload = metrics.cumulativeCellularDownload
    let wifiUpload = metrics.cumulativeWifiUpload
    let wifiDownload = metrics.cumulativeWifiDownload
}
swift
func processNetwork(_ metrics: MXNetworkTransferMetric) {
    let cellUpload = metrics.cumulativeCellularUpload
    let cellDownload = metrics.cumulativeCellularDownload
    let wifiUpload = metrics.cumulativeWifiUpload
    let wifiDownload = metrics.cumulativeWifiDownload
}

Signpost Metrics (MXSignpostMetric)

标记点指标(MXSignpostMetric)

Track custom operations with signposts:
swift
// In your code: emit signposts
import os.signpost

let log = MXMetricManager.makeLogHandle(category: "ImageProcessing")

func processImage(_ image: UIImage) {
    mxSignpost(.begin, log: log, name: "ProcessImage")
    // ... do work ...
    mxSignpost(.end, log: log, name: "ProcessImage")
}

// In metrics subscriber: read signpost data
func processSignposts(_ metrics: MXSignpostMetric) {
    let name = metrics.signpostName
    let category = metrics.signpostCategory

    // Histogram of durations
    let histogram = metrics.signpostIntervalData.histogrammedSignpostDurations

    // Total count
    let count = metrics.totalCount
}
使用标记点追踪自定义操作:
swift
// 在你的代码中:发送标记点
import os.signpost

let log = MXMetricManager.makeLogHandle(category: "ImageProcessing")

func processImage(_ image: UIImage) {
    mxSignpost(.begin, log: log, name: "ProcessImage")
    // ... 执行工作 ...
    mxSignpost(.end, log: log, name: "ProcessImage")
}

// 在指标订阅者中:读取标记点数据
func processSignposts(_ metrics: MXSignpostMetric) {
    let name = metrics.signpostName
    let category = metrics.signpostCategory

    // 持续时间直方图
    let histogram = metrics.signpostIntervalData.histogrammedSignpostDurations

    // 总计数
    let count = metrics.totalCount
}

Exporting Payload as JSON

将负载导出为JSON

swift
func exportPayload(_ payload: MXMetricPayload) {
    // JSON representation for upload to analytics
    let jsonData = payload.jsonRepresentation()

    // Or as Dictionary
    if let json = try? JSONSerialization.jsonObject(with: jsonData) as? [String: Any] {
        uploadToAnalytics(json)
    }
}
swift
func exportPayload(_ payload: MXMetricPayload) {
    // 用于上传到分析服务的JSON表示
    let jsonData = payload.jsonRepresentation()

    // 或转换为字典
    if let json = try? JSONSerialization.jsonObject(with: jsonData) as? [String: Any] {
        uploadToAnalytics(json)
    }
}

Part 3: MXDiagnosticPayload — iOS 14+

第三部分:MXDiagnosticPayload —— iOS 14+

MXDiagnosticPayload
contains diagnostic reports for crashes, hangs, disk write exceptions, and CPU exceptions.
MXDiagnosticPayload
包含崩溃、挂起、磁盘写入异常和CPU异常的诊断报告。

Payload Structure

负载结构

swift
@available(iOS 14.0, *)
func processDiagnostics(_ payload: MXDiagnosticPayload) {
    // Crash diagnostics
    if let crashes = payload.crashDiagnostics {
        for crash in crashes {
            processCrash(crash)
        }
    }

    // Hang diagnostics
    if let hangs = payload.hangDiagnostics {
        for hang in hangs {
            processHang(hang)
        }
    }

    // Disk write exceptions
    if let diskWrites = payload.diskWriteExceptionDiagnostics {
        for diskWrite in diskWrites {
            processDiskWriteException(diskWrite)
        }
    }

    // CPU exceptions
    if let cpuExceptions = payload.cpuExceptionDiagnostics {
        for cpuException in cpuExceptions {
            processCPUException(cpuException)
        }
    }
}
swift
@available(iOS 14.0, *)
func processDiagnostics(_ payload: MXDiagnosticPayload) {
    // 崩溃诊断
    if let crashes = payload.crashDiagnostics {
        for crash in crashes {
            processCrash(crash)
        }
    }

    // 挂起诊断
    if let hangs = payload.hangDiagnostics {
        for hang in hangs {
            processHang(hang)
        }
    }

    // 磁盘写入异常
    if let diskWrites = payload.diskWriteExceptionDiagnostics {
        for diskWrite in diskWrites {
            processDiskWriteException(diskWrite)
        }
    }

    // CPU异常
    if let cpuExceptions = payload.cpuExceptionDiagnostics {
        for cpuException in cpuExceptions {
            processCPUException(cpuException)
        }
    }
}

MXCrashDiagnostic

MXCrashDiagnostic

swift
@available(iOS 14.0, *)
func processCrash(_ diagnostic: MXCrashDiagnostic) {
    // Call stack tree (needs symbolication)
    let callStackTree = diagnostic.callStackTree

    // Crash metadata
    let signal = diagnostic.signal              // e.g., SIGSEGV
    let exceptionType = diagnostic.exceptionType  // e.g., EXC_BAD_ACCESS
    let exceptionCode = diagnostic.exceptionCode
    let terminationReason = diagnostic.terminationReason

    // Virtual memory info
    let virtualMemoryRegionInfo = diagnostic.virtualMemoryRegionInfo

    // Unique identifier for grouping similar crashes
    // (not available - use call stack signature)
}
swift
@available(iOS 14.0, *)
func processCrash(_ diagnostic: MXCrashDiagnostic) {
    // 调用栈树(需要符号化)
    let callStackTree = diagnostic.callStackTree

    // 崩溃元数据
    let signal = diagnostic.signal              // 例如:SIGSEGV
    let exceptionType = diagnostic.exceptionType  // 例如:EXC_BAD_ACCESS
    let exceptionCode = diagnostic.exceptionCode
    let terminationReason = diagnostic.terminationReason

    // 虚拟内存信息
    let virtualMemoryRegionInfo = diagnostic.virtualMemoryRegionInfo

    // 用于分组相似崩溃的唯一标识符
    // (不可用 - 使用调用栈签名)
}

MXHangDiagnostic

MXHangDiagnostic

swift
@available(iOS 14.0, *)
func processHang(_ diagnostic: MXHangDiagnostic) {
    // How long the hang lasted
    let duration = diagnostic.hangDuration  // Measurement<UnitDuration>

    // Call stack when hang occurred
    let callStackTree = diagnostic.callStackTree
}
swift
@available(iOS 14.0, *)
func processHang(_ diagnostic: MXHangDiagnostic) {
    // 挂起持续时间
    let duration = diagnostic.hangDuration  // Measurement<UnitDuration>

    // 挂起发生时的调用栈
    let callStackTree = diagnostic.callStackTree
}

MXDiskWriteExceptionDiagnostic

MXDiskWriteExceptionDiagnostic

swift
@available(iOS 14.0, *)
func processDiskWriteException(_ diagnostic: MXDiskWriteExceptionDiagnostic) {
    // Total bytes written that triggered exception
    let totalWrites = diagnostic.totalWritesCaused  // Measurement<UnitInformationStorage>

    // Call stack of writes
    let callStackTree = diagnostic.callStackTree
}
swift
@available(iOS 14.0, *)
func processDiskWriteException(_ diagnostic: MXDiskWriteExceptionDiagnostic) {
    // 触发异常的总写入字节数
    let totalWrites = diagnostic.totalWritesCaused  // Measurement<UnitInformationStorage>

    // 写入操作的调用栈
    let callStackTree = diagnostic.callStackTree
}

MXCPUExceptionDiagnostic

MXCPUExceptionDiagnostic

swift
@available(iOS 14.0, *)
func processCPUException(_ diagnostic: MXCPUExceptionDiagnostic) {
    // Total CPU time that triggered exception
    let totalCPUTime = diagnostic.totalCPUTime  // Measurement<UnitDuration>

    // Total sampled time
    let totalSampledTime = diagnostic.totalSampledTime

    // Call stack of CPU-intensive code
    let callStackTree = diagnostic.callStackTree
}
swift
@available(iOS 14.0, *)
func processCPUException(_ diagnostic: MXCPUExceptionDiagnostic) {
    // 触发异常的总CPU时间
    let totalCPUTime = diagnostic.totalCPUTime  // Measurement<UnitDuration>

    // 总采样时间
    let totalSampledTime = diagnostic.totalSampledTime

    // CPU密集型代码的调用栈
    let callStackTree = diagnostic.callStackTree
}

Part 4: MXCallStackTree

第四部分:MXCallStackTree

MXCallStackTree
contains stack frames from diagnostics. Frames are NOT symbolicated—you must symbolicate using your dSYM.
MXCallStackTree
包含诊断数据中的栈帧。栈帧未经过符号化——你必须使用dSYM文件进行符号化。

Structure

结构

swift
@available(iOS 14.0, *)
func parseCallStackTree(_ tree: MXCallStackTree) {
    // JSON representation
    let jsonData = tree.jsonRepresentation()

    // Parse the JSON
    guard let json = try? JSONSerialization.jsonObject(with: jsonData) as? [String: Any],
          let callStacks = json["callStacks"] as? [[String: Any]] else {
        return
    }

    for callStack in callStacks {
        guard let threadAttributed = callStack["threadAttributed"] as? Bool,
              let frames = callStack["callStackRootFrames"] as? [[String: Any]] else {
            continue
        }

        // threadAttributed = true means this thread caused the issue
        if threadAttributed {
            parseFrames(frames)
        }
    }
}

func parseFrames(_ frames: [[String: Any]]) {
    for frame in frames {
        // Binary image UUID (match to dSYM)
        let binaryUUID = frame["binaryUUID"] as? String

        // Address offset within binary
        let offsetIntoBinaryTextSegment = frame["offsetIntoBinaryTextSegment"] as? Int

        // Binary name (e.g., "MyApp", "UIKitCore")
        let binaryName = frame["binaryName"] as? String

        // Address (for symbolication)
        let address = frame["address"] as? Int

        // Sample count (how many times this frame appeared)
        let sampleCount = frame["sampleCount"] as? Int

        // Sub-frames (tree structure)
        let subFrames = frame["subFrames"] as? [[String: Any]]
    }
}
swift
@available(iOS 14.0, *)
func parseCallStackTree(_ tree: MXCallStackTree) {
    // JSON表示
    let jsonData = tree.jsonRepresentation()

    // 解析JSON
    guard let json = try? JSONSerialization.jsonObject(with: jsonData) as? [String: Any],
          let callStacks = json["callStacks"] as? [[String: Any]] else {
        return
    }

    for callStack in callStacks {
        guard let threadAttributed = callStack["threadAttributed"] as? Bool,
              let frames = callStack["callStackRootFrames"] as? [[String: Any]] else {
            continue
        }

        // threadAttributed = true 表示此线程导致了问题
        if threadAttributed {
            parseFrames(frames)
        }
    }
}

func parseFrames(_ frames: [[String: Any]]) {
    for frame in frames {
        // 二进制镜像UUID(与dSYM匹配)
        let binaryUUID = frame["binaryUUID"] as? String

        // 二进制文本段内的地址偏移
        let offsetIntoBinaryTextSegment = frame["offsetIntoBinaryTextSegment"] as? Int

        // 二进制名称(例如:"MyApp", "UIKitCore")
        let binaryName = frame["binaryName"] as? String

        // 地址(用于符号化)
        let address = frame["address"] as? Int

        // 采样计数(此栈帧出现的次数)
        let sampleCount = frame["sampleCount"] as? Int

        // 子栈帧(树状结构)
        let subFrames = frame["subFrames"] as? [[String: Any]]
    }
}

JSON Structure Example

JSON结构示例

json
{
  "callStacks": [
    {
      "threadAttributed": true,
      "callStackRootFrames": [
        {
          "binaryUUID": "A1B2C3D4-E5F6-7890-ABCD-EF1234567890",
          "offsetIntoBinaryTextSegment": 123456,
          "binaryName": "MyApp",
          "address": 4384712345,
          "sampleCount": 10,
          "subFrames": [
            {
              "binaryUUID": "F1E2D3C4-B5A6-7890-1234-567890ABCDEF",
              "offsetIntoBinaryTextSegment": 78901,
              "binaryName": "UIKitCore",
              "address": 7234567890,
              "sampleCount": 10
            }
          ]
        }
      ]
    }
  ]
}
json
{
  "callStacks": [
    {
      "threadAttributed": true,
      "callStackRootFrames": [
        {
          "binaryUUID": "A1B2C3D4-E5F6-7890-ABCD-EF1234567890",
          "offsetIntoBinaryTextSegment": 123456,
          "binaryName": "MyApp",
          "address": 4384712345,
          "sampleCount": 10,
          "subFrames": [
            {
              "binaryUUID": "F1E2D3C4-B5A6-7890-1234-567890ABCDEF",
              "offsetIntoBinaryTextSegment": 78901,
              "binaryName": "UIKitCore",
              "address": 7234567890,
              "sampleCount": 10
            }
          ]
        }
      ]
    }
  ]
}

Symbolication

符号化

MetricKit call stacks are unsymbolicated. To symbolicate:
  1. Keep your dSYM files for every App Store build
  2. Match UUID from
    binaryUUID
    to your dSYM
  3. Use atos to symbolicate:
bash
undefined
MetricKit的调用栈是未符号化的。要进行符号化:
  1. 保留每个App Store构建的dSYM文件
  2. binaryUUID
    与你的dSYM文件匹配
  3. 使用atos工具进行符号化
bash
undefined

Find dSYM for binary UUID

根据二进制UUID查找dSYM

mdfind "com_apple_xcode_dsym_uuids == A1B2C3D4-E5F6-7890-ABCD-EF1234567890"
mdfind "com_apple_xcode_dsym_uuids == A1B2C3D4-E5F6-7890-ABCD-EF1234567890"

Symbolicate address

符号化地址

atos -arch arm64 -o MyApp.app.dSYM/Contents/Resources/DWARF/MyApp -l 0x100000000 0x105234567

Or use a crash reporting service that handles symbolication (Crashlytics, Sentry, etc.).
atos -arch arm64 -o MyApp.app.dSYM/Contents/Resources/DWARF/MyApp -l 0x100000000 0x105234567

或使用支持符号化的崩溃报告服务(如Crashlytics、Sentry等)。

Part 5: MXBackgroundExitData

第五部分:MXBackgroundExitData

Track why your app was terminated in the background:
swift
@available(iOS 14.0, *)
func analyzeBackgroundExits(_ data: MXBackgroundExitData) {
    // Normal exits (user closed, system reclaimed)
    let normal = data.cumulativeNormalAppExitCount

    // Memory issues
    let memoryLimit = data.cumulativeMemoryResourceLimitExitCount  // Exceeded memory limit
    let memoryPressure = data.cumulativeMemoryPressureExitCount    // Jetsam

    // Crashes
    let badAccess = data.cumulativeBadAccessExitCount        // SIGSEGV
    let illegalInstruction = data.cumulativeIllegalInstructionExitCount  // SIGILL
    let abnormal = data.cumulativeAbnormalExitCount          // Other crashes

    // System terminations
    let watchdog = data.cumulativeAppWatchdogExitCount       // Timeout during transition
    let taskTimeout = data.cumulativeBackgroundTaskAssertionTimeoutExitCount  // Background task timeout
    let cpuLimit = data.cumulativeCPUResourceLimitExitCount  // Exceeded CPU quota
    let lockedFile = data.cumulativeSuspendedWithLockedFileExitCount  // File lock held
}
追踪应用在后台被终止的原因:
swift
@available(iOS 14.0, *)
func analyzeBackgroundExits(_ data: MXBackgroundExitData) {
    // 正常退出(用户关闭、系统回收)
    let normal = data.cumulativeNormalAppExitCount

    // 内存问题
    let memoryLimit = data.cumulativeMemoryResourceLimitExitCount  // 超出内存限制
    let memoryPressure = data.cumulativeMemoryPressureExitCount    // Jetsam(系统回收)

    // 崩溃
    let badAccess = data.cumulativeBadAccessExitCount        // SIGSEGV
    let illegalInstruction = data.cumulativeIllegalInstructionExitCount  // SIGILL
    let abnormal = data.cumulativeAbnormalExitCount          // 其他崩溃

    // 系统终止
    let watchdog = data.cumulativeAppWatchdogExitCount       // 过渡期间超时
    let taskTimeout = data.cumulativeBackgroundTaskAssertionTimeoutExitCount  // 后台任务超时
    let cpuLimit = data.cumulativeCPUResourceLimitExitCount  // 超出CPU配额
    let lockedFile = data.cumulativeSuspendedWithLockedFileExitCount  // 挂起时持有文件锁
}

Exit Type Interpretation

退出类型解读

Exit TypeMeaningAction
normalAppExitCount
Clean exitNone (expected)
memoryResourceLimitExitCount
Used too much memoryReduce footprint
memoryPressureExitCount
Jetsam (system reclaimed)Reduce background memory to <50MB
badAccessExitCount
SIGSEGV crashCheck null pointers, invalid memory
illegalInstructionExitCount
SIGILL crashCheck invalid function pointers
abnormalExitCount
Other crashCheck crash diagnostics
appWatchdogExitCount
Hung during transitionReduce launch/background work
backgroundTaskAssertionTimeoutExitCount
Didn't end background taskCall
endBackgroundTask
properly
cpuResourceLimitExitCount
Too much background CPUMove to BGProcessingTask
suspendedWithLockedFileExitCount
Held file lock while suspendedRelease locks before suspend
退出类型含义操作建议
normalAppExitCount
正常退出无需操作(预期行为)
memoryResourceLimitExitCount
内存使用过多减少内存占用
memoryPressureExitCount
Jetsam(系统回收)将后台内存占用降至50MB以下
badAccessExitCount
SIGSEGV崩溃检查空指针、无效内存
illegalInstructionExitCount
SIGILL崩溃检查无效函数指针
abnormalExitCount
其他崩溃查看崩溃诊断数据
appWatchdogExitCount
过渡期间挂起减少启动/后台工作负载
backgroundTaskAssertionTimeoutExitCount
未结束后台任务正确调用
endBackgroundTask
cpuResourceLimitExitCount
后台CPU占用过高迁移至BGProcessingTask
suspendedWithLockedFileExitCount
挂起时持有文件锁挂起前释放锁

Part 6: Integration Patterns

第六部分:集成模式

Upload to Analytics Service

上传到分析服务

swift
class MetricsUploader {
    func upload(_ payload: MXMetricPayload) {
        let jsonData = payload.jsonRepresentation()

        var request = URLRequest(url: analyticsEndpoint)
        request.httpMethod = "POST"
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        request.httpBody = jsonData

        URLSession.shared.dataTask(with: request) { _, response, error in
            if let error = error {
                // Queue for retry
                self.queueForRetry(jsonData)
            }
        }.resume()
    }
}
swift
class MetricsUploader {
    func upload(_ payload: MXMetricPayload) {
        let jsonData = payload.jsonRepresentation()

        var request = URLRequest(url: analyticsEndpoint)
        request.httpMethod = "POST"
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        request.httpBody = jsonData

        URLSession.shared.dataTask(with: request) { _, response, error in
            if let error = error {
                // 加入重试队列
                self.queueForRetry(jsonData)
            }
        }.resume()
    }
}

Combine with Crash Reporter

与崩溃报告工具结合

swift
class HybridCrashReporter: MXMetricManagerSubscriber {
    let crashlytics: Crashlytics // or Sentry, etc.

    func didReceive(_ payloads: [MXDiagnosticPayload]) {
        for payload in payloads {
            // MetricKit captures crashes that traditional reporters might miss
            // (e.g., watchdog kills, memory pressure exits)

            if let crashes = payload.crashDiagnostics {
                for crash in crashes {
                    crashlytics.recordException(
                        name: crash.exceptionType?.description ?? "Unknown",
                        reason: crash.terminationReason ?? "MetricKit crash",
                        callStack: parseCallStack(crash.callStackTree)
                    )
                }
            }
        }
    }
}
swift
class HybridCrashReporter: MXMetricManagerSubscriber {
    let crashlytics: Crashlytics // 或Sentry等

    func didReceive(_ payloads: [MXDiagnosticPayload]) {
        for payload in payloads {
            // MetricKit能捕获传统报告工具可能遗漏的崩溃
            // (例如:看门狗终止、内存压力退出)

            if let crashes = payload.crashDiagnostics {
                for crash in crashes {
                    crashlytics.recordException(
                        name: crash.exceptionType?.description ?? "Unknown",
                        reason: crash.terminationReason ?? "MetricKit crash",
                        callStack: parseCallStack(crash.callStackTree)
                    )
                }
            }
        }
    }
}

Alert on Regressions

性能退化告警

swift
class MetricsMonitor: MXMetricManagerSubscriber {
    let thresholds = MetricThresholds(
        launchTime: 2.0,  // seconds
        hangRate: 0.01,   // 1% of sessions
        memoryPeak: 200   // MB
    )

    func didReceive(_ payloads: [MXMetricPayload]) {
        for payload in payloads {
            checkThresholds(payload)
        }
    }

    private func checkThresholds(_ payload: MXMetricPayload) {
        // Check launch time
        if let launches = payload.applicationLaunchMetrics {
            let p50 = calculateP50(launches.histogrammedTimeToFirstDraw)
            if p50 > thresholds.launchTime {
                sendAlert("Launch time regression: \(p50)s > \(thresholds.launchTime)s")
            }
        }

        // Check memory
        if let memory = payload.memoryMetrics {
            let peakMB = memory.peakMemoryUsage.converted(to: .megabytes).value
            if peakMB > Double(thresholds.memoryPeak) {
                sendAlert("Memory peak regression: \(peakMB)MB > \(thresholds.memoryPeak)MB")
            }
        }
    }
}
swift
class MetricsMonitor: MXMetricManagerSubscriber {
    let thresholds = MetricThresholds(
        launchTime: 2.0,  // 秒
        hangRate: 0.01,   // 1%的会话
        memoryPeak: 200   // MB
    )

    func didReceive(_ payloads: [MXMetricPayload]) {
        for payload in payloads {
            checkThresholds(payload)
        }
    }

    private func checkThresholds(_ payload: MXMetricPayload) {
        // 检查启动时间
        if let launches = payload.applicationLaunchMetrics {
            let p50 = calculateP50(launches.histogrammedTimeToFirstDraw)
            if p50 > thresholds.launchTime {
                sendAlert("启动时间退化:\(p50)s > \(thresholds.launchTime)s")
            }
        }

        // 检查内存
        if let memory = payload.memoryMetrics {
            let peakMB = memory.peakMemoryUsage.converted(to: .megabytes).value
            if peakMB > Double(thresholds.memoryPeak) {
                sendAlert("内存峰值退化:\(peakMB)MB > \(thresholds.memoryPeak)MB")
            }
        }
    }
}

Part 7: Best Practices

第七部分:最佳实践

Do

建议

  • Register subscriber early — In
    application(_:didFinishLaunchingWithOptions:)
    or App init
  • Keep dSYM files — Required for symbolicating call stacks
  • Upload payloads to server — Local processing loses data on uninstall
  • Set up alerting — Detect regressions before users report them
  • Test with simulated payloads — Xcode Debug menu in iOS 15+
  • 尽早注册订阅者 — 在
    application(_:didFinishLaunchingWithOptions:)
    或App初始化时
  • 保留dSYM文件 — 调用栈符号化必需
  • 将负载上传到服务器 — 本地处理会在卸载时丢失数据
  • 设置告警 — 在用户反馈前检测性能退化
  • 使用模拟负载测试 — iOS 15+中使用Xcode Debug菜单

Don't

不建议

  • Don't rely solely on MetricKit — 24-hour delay, requires user opt-in
  • Don't ignore background exits — Jetsam and task timeouts affect UX
  • Don't skip symbolication — Raw addresses are unusable
  • Don't process on main thread — Payload processing can be expensive
  • 不要仅依赖MetricKit — 24小时延迟、需要用户选择加入
  • 不要忽略后台退出 — Jetsam和任务超时会影响用户体验
  • 不要跳过符号化 — 原始地址无法使用
  • 不要在主线程处理 — 负载处理可能消耗大量资源

Privacy Considerations

隐私注意事项

  • MetricKit data is aggregated and anonymized
  • Data only from users who opted into sharing analytics
  • No personally identifiable information
  • Safe to upload to your servers
  • MetricKit数据是聚合且匿名的
  • 仅来自选择共享分析数据的用户
  • 不包含个人可识别信息
  • 可安全上传至你的服务器

Part 8: MetricKit vs Xcode Organizer

第八部分:MetricKit vs Xcode Organizer

FeatureMetricKitXcode Organizer
Data sourceDevices running your appApp Store Connect aggregation
DeliveryDaily to your subscriberOn-demand in Xcode
CustomizationFull access to raw dataPredefined views
SymbolicationYou must symbolicatePre-symbolicated
Historical dataOnly when subscriber activeLast 16 versions
Requires codeYesNo
Use both: Organizer for quick overview, MetricKit for custom analytics and alerting.
功能MetricKitXcode Organizer
数据源运行应用的设备App Store Connect聚合数据
推送方式每日推送给订阅者Xcode中按需获取
自定义能力完全访问原始数据预定义视图
符号化需自行处理已预符号化
历史数据仅订阅者活跃期间的数据最近16个版本
是否需要代码
建议结合使用:Organizer用于快速概览,MetricKit用于自定义分析与告警。

Resources

资源

WWDC: 2019-417, 2020-10081, 2021-10087
Docs: /metrickit, /metrickit/mxmetricmanager, /metrickit/mxdiagnosticpayload
Skills: axiom-hang-diagnostics, axiom-performance-profiling, axiom-testflight-triage
WWDC:2019-417, 2020-10081, 2021-10087
文档:/metrickit, /metrickit/mxmetricmanager, /metrickit/mxdiagnosticpayload
相关技能:axiom-hang-diagnostics, axiom-performance-profiling, axiom-testflight-triage