Meet the new MetricKit
Xcode & Developer Tools 进阶 17m

了解全新的 MetricKit

Meet the new MetricKit

2026年6月10日

在 Apple 官方观看视频

一句话判断

这场 Session 最值得关注的一件事是:Apple 终于用全新的 StateReporting 框架解决了“全局平均指标掩盖特定页面卡顿”的千古难题。

这场 Session 讲了什么

以前我们用 MetricKit(指标套件)看性能,拿到的是个“大杂烩”。App 整体启动慢了 200ms,或者平均 Hang(卡顿)率升了,你根本不知道是首页列表卡了,还是深层级的一个冷门设置页在作妖。现在 Apple 把 MetricKit 用纯 Swift 从头重写了一遍,抛弃了老旧的 Delegate 模式,全面拥抱 AsyncSequence。

最大的杀器是引入了 StateReporting(状态上报)框架。它允许你把 App 的运行状态(比如当前在哪个 Tab、处于什么用户流)作为维度,和性能指标做交叉分析。这意味着你可以精准看到“用户在查看大尺寸列表且未排序时,内存峰值到底是多少”。这对中大型 App 和需要精细化调优的游戏开发者来说,是直接提升排查效率的利器。

值得深挖的点

StateReporting:打破“平均数陷阱”

做性能监控最怕看平均数。一个 99% 时间都流畅的 App,如果那 1% 的卡顿发生在支付页,那就是灾难。Apple 推出 StateReporting 就是为了解决这个痛点。旧方案里,我们要自己记录页面停留时间,再和 MetricKit 的按小时切片数据做复杂的时序对齐,简直反人类。

现在,你只需定义一个 StateReportingDomain(状态上报域),在页面切换时调用 reportTransition(to:)。MetricKit 会在底层自动把指标按这些 State(状态)进行聚合。更绝的是 @ReportableMetadata 宏,你可以把页面的配置(比如列表大小、是否排序)作为元数据绑上去。

这里的 trade-off 很明显:Apple 把“定义什么是重要状态”的脏活交给了开发者。如果你乱埋点,State 碎片化,数据就废了。但只要你设计好 State 树,它比任何第三方 APM 的页面级监控都要准,因为它是系统级采集,没有采样率折损,也不吃你的业务 CPU。

Swift-first 重构与 OOM 诊断的补全

老版 MetricKit 的 Objective-C 痕迹很重,JSON 解析更是让人头皮发麻。这次 iOS 27 直接给出了 MetricManager 和 AsyncSequence,代码量锐减。通过 JSONEncoder 直接 encode report 发回服务端,省去了手写解析模型的痛苦。

另一个大惊喜是 Memory Exception Diagnostics(内存异常诊断)。以前 App 被系统因为 OOM(内存溢出)杀掉,MetricKit 只能给个干瘪的退出信号,开发者只能靠猜或者接第三方 SDK。现在,系统会直接下发包含符号化堆栈和终止原因的诊断报告,配合新增的 terminationCategory(终止类别)字段,排查内存泄漏和内存尖刺终于有了官方实锤。

代码片段

1. 结合 StateReporting 接收细分指标

场景:在 App 启动时初始化 MetricManager,并监听带有状态维度的每日指标报告。

import MetricKit
import StateReporting

// 定义你的状态域,通常和 Bundle ID 相关
let domain = StateReportingDomain("com.myapp.tabs")
let manager = MetricManager(enabledStateReportingDomains: [domain])

// 使用 AsyncSequence 监听指标报告
Task {
    for await report in manager.metricReports {
        // 获取全天的指标入口
        let fullDayEntry = report.intervalEntries.fullDayEntry
        
        // 遍历按状态细分的指标
        for stateEntry in fullDayEntry.stateEntries {
            let stateName = stateEntry.stateName
            // 过滤出内存组的指标
            let memoryMetrics = stateEntry.values.filter { $0.metricGroup == .memory }
            processMemoryMetrics(state: stateName, metrics: memoryMetrics)
        }
    }
}

坑:MetricManager 必须在 App 启动的极早期(如 application(_:didFinishLaunchingWithOptions:))初始化并持有,否则会导致数据流订阅延迟,丢失当天的部分数据。

2. 捕获并上报内存异常诊断

场景:处理系统下发的诊断报告,特别是新增的内存异常(Memory Exception)和崩溃分类。

import MetricKit

let manager = MetricManager()

Task {
    for await report in manager.diagnosticReports {
        switch report.result {
        case .crash(let crash):
            // 利用新增的 terminationCategory 快速分类崩溃
            let category = crash.terminationCategory
            let reason = crash.terminationReason
            uploadCrash(category: category, reason: reason, backtrace: crash.callStackTree)
            
        case .memoryException(let memoryException):
            // 终于能拿到 OOM 时的详细堆栈和元数据了
            let footprint = memoryException.footprint
            let backtrace = memoryException.callStackTree
            uploadOOM(footprint: footprint, backtrace: backtrace)
            
        default:
            break
        }
    }
}

坑:诊断报告包含敏感的用户设备信息,直接 JSONEncoder 上报到自建服务端时,务必确保传输链路加密,并符合隐私合规要求。

最佳实践

老项目迁移不要想着一步到位。先把 MXMetricManager 替换为新的 MetricManager,跑通 AsyncSequence 的数据上报链路。确认服务端能正确解析新的 Codable 结构后,再逐步引入 StateReporting。

新项目强烈建议第一天就把 StateReporting 规划进架构里。把 App 的核心 Tab、关键用户流(如“结账流程”、“视频播放”)定义为独立的 State。不要在每个 ViewController 的 viewDidAppear 里无脑上报 State,这会导致状态碎片化,让交叉分析失去意义。

实战中最容易踩的坑是 State 的生命周期管理。如果用户切到后台,你的 State 没有正确 reset 或者 pause,MetricKit 会把后台挂起的时间也算进这个 State 的 Hang 或 CPU 指标里,导致数据严重失真。

还有什么值得关注

  • Metal 帧率指标:游戏开发者狂喜,终于能直接在 MetricKit 里拿到 Metal 渲染的真实帧率分布,不用自己写 HUD 统计了。
  • @ReportableMetadata:极大简化了自定义上下文数据的绑定,编译器会帮你生成底层的序列化代码,比手动拼 Dictionary 优雅太多。
  • Crash 终止类别 (terminationCategory):给崩溃报告打上了更细粒度的系统级标签,配合服务端做聚合分析时,能省去大量正则匹配 terminationReason 的脏活。
MetricKit StateReporting Performance Diagnostics Swift