WWDC Quick Look 💓 By SwiftGGTeam
Meet the new MetricKit

Meet the new MetricKit

观看原视频

Highlight

iOS 27 将 MetricKit 用 Swift 从头重写,引入全新的 MetricManagerStateReporting 框架,让开发者可以把性能指标按 App 状态(如当前 Tab、用户流)做交叉分析,并新增了内存异常诊断和 Metal 帧率指标。

核心内容

你上线了一个 App,用户反馈”有点卡”。你打开 Xcode Instruments,一切正常。问题在哪?真机上的性能问题和模拟器里完全不一样。用户可能在旧设备上、在电量低时、在网络差的环境中使用你的 App。你需要的是真实世界里的数据。

MetricKit 就是干这个的。它每天把 App 在真实设备上的性能数据打包成报告,发到你的 App 里。你可以把这些数据上传到自己的服务器,聚合分析,找到问题。

但老版 MetricKit 有个硬伤:所有指标都是全局平均的。你的 App 有三个 Tab,MetricKit 告诉你”全天平均滚动卡顿率是 15ms/s”。这数字本身没问题,但你不知道卡顿发生在哪个 Tab。可能 Spending Tab 流畅得像黄油,Reports Tab 却卡得没法用。全局平均把问题埋没了。

iOS 27 的解法是两个新东西:一套全新的 Swift-first API,和一个叫 StateReporting 的框架。

新 API 抛弃了老旧的 delegate 回调,改用 async for 循环。MetricManager 同时提供 metricReportsdiagnosticReports 两个 AsyncSequence,代码写起来干净很多。报告本身也变成了 Codable,直接用 JSONEncoder 编码就能发服务器,不用再手写解析逻辑。

StateReporting 框架解决的是”指标该按什么维度拆分”的问题。你定义 App 的关键状态——比如当前在哪个 Tab、处于什么用户流——MetricKit 就会把指标按这些状态分别聚合。回到前面的例子,加上 StateReporting 后,你会发现 Spending Tab 的卡顿率只有 1ms/s,而 Reports Tab 高达 71ms/s。优化目标立刻清晰了。

详细内容

全新的 MetricManager API

00:04:58

iOS 27 的 MetricKit 用纯 Swift 重写。入口从 MXMetricManager 变成了 MetricManager

import MetricKit

let manager = MetricManager()

for await report in manager.metricReports {
    processReport(report)
}

关键点:

  • MetricManager() 是新的入口类,替代了旧版的 MXMetricManager
  • metricReportsAsyncSequence,用 for await 接收每日报告
  • 这段代码要在 App 启动时尽早执行,延迟订阅会丢失当天数据
  • MetricManager 需要保持存活,让流持续接收后续报告

00:05:25

报告是 Codable 的,编码发服务器只需几行:

import MetricKit

for await report in manager.metricReports {
    let jsonData = try JSONEncoder().encode(report)
    sendToServer(jsonData)
}

关键点:

  • MetricReport 遵循 Codable 协议
  • 直接用 JSONEncoder 编码整个报告,无需手写解析模型
  • sendToServer 是你自己的网络层方法

00:05:44

如果你想在本地分析特定指标,可以遍历报告里的区间条目:

import MetricKit

for await report in manager.metricReports {
    let intervalEntries = report.intervalEntries
    let fullDayEntry = intervalEntries.fullDayEntry

    for entry in intervalEntries {
        let memoryMetrics = entry.values.filter { $0.metricGroup == .memory }

        for metric in memoryMetrics {
            switch metric {
            case .peakMemory(let peak):
                processPeakMemory(peak)
            default: break
            }
        }
    }
}

关键点:

  • intervalEntries 包含全天汇总条目和若干小时级细分条目
  • fullDayEntry 是全天的聚合数据
  • 每个条目里的 valuesmetricGroup 分组,如 .memory.cpu.display.gpu
  • switch 匹配具体指标类型,如 .peakMemory
  • 这段工作要放在 detached Task 或专门的服务类里

诊断报告:从指标到根因

00:08:59

指标告诉你”有问题”,诊断告诉你”问题在哪”。

import MetricKit

let manager = MetricManager()

for await report in manager.diagnosticReports {
    processReport(report)
}

关键点:

  • diagnosticReports 也是 AsyncSequence
  • 诊断报告在事件发生时立即下发,不等每日汇总
  • 同样需要在 App 启动时尽早监听

00:09:14

诊断报告同样支持 Codable

import MetricKit

for await report in manager.diagnosticReports {
    let jsonData = try JSONEncoder().encode(report)
    sendToServer(jsonData)
}

00:09:39

结构化访问诊断内容:

import MetricKit

for await report in manager.diagnosticReports {
    switch report.result {
    case .crash(let crash):
        let backtrace = crash.callStackTree
        let reason = crash.terminationReason
        let category = crash.terminationCategory
        processCrash(backtrace: backtrace, reason: reason, category: category)
    case .hang(let hang):
        processHangDiagnostic(hang)
    default: break
    }
}

关键点:

  • report.result 用枚举区分诊断类型:.crash.hang.memoryException
  • crash.callStackTree 是符号化堆栈,直接指向问题代码
  • terminationReason 说明崩溃原因
  • terminationCategory 是 iOS 27 新增字段,说明崩溃在指标中如何归类,方便把诊断和指标关联起来

StateReporting:按 App 状态拆分指标

00:13:57

这是 iOS 27 MetricKit 最核心的新能力。你定义状态,MetricKit 按状态聚合指标。

import MetricKit
import StateReporting

let domain = StateReportingDomain("com.metrickitsample.tabs")
let manager = MetricManager(enabledStateReportingDomains: [domain])

let reporter = StateReporter.reporter(for: domain.rawValue)
reporter.reportTransition(to: "Reports")

关键点:

  • StateReportingDomain 定义一个状态域,通常用反向 DNS 格式
  • 创建 MetricManager 时把域传进 enabledStateReportingDomains
  • StateReporter.reporter(for:) 获取该域的报告器
  • reportTransition(to:) 上报当前进入的状态,没有”结束”概念,只有”切换到”

00:14:21

@ReportableMetadata 宏附加结构化元数据:

import StateReporting

@ReportableMetadata
struct ViewConfiguration {
    let listSize: String
    let isSorted: Bool
}

let reporter = StateReporter.reporter(
    for: domain.rawValue,
    stableMetadata: ViewConfiguration.self
)

reporter.reportTransition(
    to: "Reports",
    stableMetadata: ViewConfiguration(listSize: "large", isSorted: false)
)

关键点:

  • @ReportableMetadata 宏标记自定义结构体,编译器自动生成序列化代码
  • stableMetadata 参数指定元数据类型
  • 上报状态时同时传入具体的元数据实例
  • 这样 MetricKit 就能区分”Reports Tab 的大列表未排序”和”Reports Tab 的小列表已排序”的性能差异

00:15:29

编码时按状态分组:

import MetricKit

for await report in manager.metricReports {
    let encoder = JSONEncoder()

    let formatKey = MetricReport.encodingFormatKey
    encoder.userInfo[formatKey] = MetricReport.EncodingFormat.byStateReportingDomain

    let jsonData = try encoder.encode(report)
    sendToServer(jsonData)
}

关键点:

  • MetricReport.encodingFormatKey 是编码格式的配置键
  • MetricReport.EncodingFormat.byStateReportingDomain 让 JSON 按状态域和状态分组输出
  • 服务端收到后可以直接按状态维度做聚合分析

iOS 27 新增指标和诊断

00:03:23

Metal 帧率指标。游戏开发者可以直接在 MetricKit 里拿到渲染帧率的分布数据,不用再自己写统计逻辑。

00:03:55

内存异常诊断。App 或扩展因超出内存限制被系统终止时,MetricKit 会下发包含堆栈和终止原因的诊断报告。这是以前没有的。

核心启发

1. 给每个核心用户流做性能画像

用 StateReporting 把 App 的关键流程(首页加载、商品详情、支付确认、视频播放)定义为独立状态。上线一周后看数据,哪个流程的 Hang 率或内存峰值异常,一目了然。入口是 StateReportingDomainStateReporter.reportTransition(to:)

2. 在 A/B 实验里自动采集性能数据

把实验分组也作为一个 StateReporting 域。实验组和对照组的性能指标自动分开统计,不用在实验代码里手动埋性能点。实验效果不仅看业务指标,也看性能指标。

3. 搭建实时诊断告警

诊断报告是即时下发的,不是每日汇总。在服务端收到 Crash 或 Memory Exception 诊断后,立刻触发告警。配合 terminationCategory 字段,可以区分”需要紧急修复的崩溃”和”已知的系统级终止”。

4. 给复杂列表页做配置级性能监控

@ReportableMetadata 把列表页的配置(数据量大小、是否排序、是否启用图片懒加载)作为元数据上报。发现”大列表+未排序”组合下滚动卡顿率飙升,优化方向立刻清晰。

5. 从旧版 MXMetricManager 迁移

如果你已经在用 MXMetricManager,迁移路径很直接:把 delegate 回调换成 for await 循环,把手动 JSON 解析换成 JSONEncoder().encode(report)。新 API 是框架的未来,所有新功能都只在新 API 上提供。

关联 Session

评论

GitHub Issues · utterances