← 所有文章

MetricKit 全面重建:iOS 27 中的状态感知遥测

Apple 在 WWDC 2026 的演示 App 报告的滚动卡顿率为每秒 15 毫秒,这是整天使用量的平均值。然而按标签页拆分后,同一份数据讲的却是截然不同的故事:一个标签页是 1 ms/s,另一个则是 71 ms/s。1一个画面近乎完美,另一个用该场次的说法是「正经历严重中断」。1这个混合后的数字把两件事都掩盖了。第 222 场「Meet the new MetricKit」讲述的正是 iOS 27 如何弥合这道落差:彻底重建框架的 API 界面,并推出全新的配套框架 StateReporting,把整个 App 的现场指标转换成按状态区分的指标。现场遥测终于能回答每位性能工程师最先问的问题:到底是哪个画面慢?

TL;DR

  • 在 iOS 27 中,MetricKit「已从头重建,采用情境丰富、表达力强的现代 Swift 优先 API」,而该场次介绍的每一项新功能都是这套新 API 独有的。1
  • 入口是 MetricManager 类。App 在启动时 await metricReportsdiagnosticReports 两个异步流,而这两种报告类型都符合 Codable,因此用一个 JSONEncoder 就能直接把它们发送到您的分析服务器。1
  • 报告是结构化的:intervalEntries 包含一条整日的条目,以及较小的细分区间,并按 .cpu.memory.display.gpu 等指标分组来组织,最终可深入到 peakMemory 这类单个数值。1
  • iOS 27 的新数据:用于衡量渲染性能的 Metal 帧率指标、针对超出内存上限而终止的内存异常诊断,以及一个把单次崩溃诊断与您的指标趋势串联起来的崩溃 category1
  • 最受瞩目的功能是 StateReporting 框架:报告您 App 所处的状态(当前标签页、实验分组、视图配置),MetricKit 便会按状态汇总指标,把单一的混合数字换成按画面区分的细目。1

从头重建

观看:Meet the new MetricKit (WWDC26)

MetricKit 团队的工程师 Yonni 从 1:23 开始介绍 iOS 27 的重建。

MetricKit 的任务并未改变:它是性能工作流程中「负责采集的部分」,提供两类数据。指标告诉您某个性能方面整体上是在改善还是恶化;诊断则告诉您是哪条代码路径造成了问题。1改变的是您接收这些数据的整套方式。该场次说得很直接:在 iOS 27 中,这个框架「已从头重建,采用情境丰富、表达力强的现代 Swift 优先 API」,而且「我今天要谈的所有进展,都是这套全新 API 独有的」。1

新的入口是 MetricManager 类。您不再需要注册 delegate 并解析 payload,而是通过 metricReports 属性以异步流的形式 await 报告。有两条操作原则直接出自该场次:在 App 启动时就完成设置,「以避免因延迟订阅而造成任何数据丢失」,并让 MetricManager 保持存活,「这样流才能在后续数据就绪时持续传递报告」。1Apple 建议在 App 一启动时,就用一个 detached task 或专属的服务类来执行这些工作。1

该场次是在幻灯片上展示代码,因此以下的片段是符合其描述的示意调用形式;在正式发布前,请对照 Apple 的文档确认确切的签名。

// Illustrative call shape based on session 222; verify against the docs.
let manager = MetricManager()

Task.detached {
    for await report in manager.metricReports {
        // Encode and ship, or inspect specific groups.
    }
}

以往要把报告发送到服务器,得处理不透明的 payload 数据。如今 MetricReport 值都符合 Codable:「只要创建一个 JSONEncoder,把整份报告编码即可。」1如果您要的是某个特定数值而非整份文档,报告也是完全结构化的。您可以遍历 intervalEntries,它「包含一条整日汇总的条目,以及可用时的较小细分窗口」,每个窗口通常为数小时,且只有在有对应指标时才会存在。1在每个区间内,指标都按指标分组组织,其中「每个分组代表系统的一个方面,例如 .cpu.memory.display.gpu」。1筛选到您关心的分组(该场次的示例取出 memoryMetrics),再对各指标 case 进行 switch,便能取得 peakMemory 这类单个数值。1

iOS 27 的指标目录也在扩充。除了启动时间直方图(该场次示例显示大多数启动落在 510 到 540 毫秒之间)、卡顿、动画指标,以及 CPU、GPU、磁盘写入、网络传输等资源消耗之外,MetricKit 还新增了 Metal 帧率指标。该场次称帧率是「游戏开发者理解渲染性能的关键指标」,并在优化方面指向「Find and fix performance issues in your Metal game」。1

诊断:回溯追踪、内存异常与崩溃分类

指标告诉您有东西退步了,诊断则告诉您退步在哪里。当出问题时,例如崩溃或卡顿,「系统会在设备上捕获一条诊断」,而一份诊断报告会「把细节打包好,并通过 MetricKit 立即发送到您的 App」。1许多诊断都包含回溯追踪,呈现事件发生当下的确切调用栈。在该场次的逐步演示中,符号化后的回溯追踪从系统代码中的线程起点开始,跨入 App,最后停在 App 的 submitReport() 函数,标示出失败点,也就是修复应该瞄准的地方。1

崩溃诊断会带有回溯追踪、终止原因与异常类型。iOS 27 新增的终止 category「标示出每次崩溃在指标中是如何计入的」,因此「若异常终止呈现上升趋势,您可以把它们直接与单个诊断做关联」。1您仪表板上的指标折线,以及其背后的单个崩溃报告,终于共用同一把钥匙。

iOS 27 也新增了内存异常诊断:「当您的 App 或扩展因超出内存上限而被终止时,您能对发生了什么获得更多洞察。」1扩展明确涵盖在范围内,这对任何要远程调试小组件或扩展内存被终止的人而言都很重要。

接收端与指标端如出一辙。您在自己的 MetricManager 实例上 await diagnosticReports,同样在 App 启动时于 detached task 或服务类中进行,而 DiagnosticReport 值也符合 Codable,可走同一条编码后发送的流程。1由于报告是结构化的,您可以对各诊断 case 进行 switch:崩溃 case 会给出回溯追踪、原因与 category,而卡顿 case 则可导向不同的处理方式。1

// Illustrative call shape based on session 222; verify against the docs.
for await report in manager.diagnosticReports {
    switch /* diagnostic case */ {
    case /* crash */: break  // backtrace, reason, category
    case /* hang */:  break  // handle separately
    default:          break
    }
}

StateReporting:从单一混合数字到按画面呈现的真相

以上所有内容描述的仍是整个 App 的遥测,而整个 App 的遥测有其天花板。该场次的报销 App 把问题具体化了。这个 App 把功能组织成一个 Reports 标签页与一个 Spending 标签页。在一天当中,MetricKit 报告了 5 分钟滚动里共 4.5 秒的卡顿时间:卡顿率为 15 ms/s。但这个数字是「整个 App 使用过程的平均滚动卡顿率,即使有人在 Reports 标签页与 Spending 标签页之间来回切换也一样」。1您知道 App 会卡,却不知道卡在哪里。

全新的 StateReporting 框架去除了这种混合。状态是「您定义的信息,用来描述 App 的配置或行为,好让 MetricKit 能以这些特征为函数来汇总指标」。1当用户在标签页之间移动时,App 会报告每一次转换,而 MetricKit 会把这些状态与指标及诊断数据相交。1

演示中的成果,正是让这场重建显得值得的时刻。指标不再是单一的混合 15 ms/s 数字,而是按状态送达:Spending 标签页滚动起来「顺得不可思议」,为 1 ms/s,而 Reports 标签页则「飙升到 71 ms/s」。1该场次下了混合数字永远无从支撑的结论:「Spending 标签页表现得很棒!但 Reports 标签页正经历严重中断,而那正是您的优化精力该聚焦之处。」1一个数字变成了一项判断与一份工作清单。

状态遵循的是转换模型,而非成对括起。「没有起止配对——App 在任一时刻报告它当下所处的状况」,而 MetricKit 会追踪 App 停留在每个状态的时间长短。1

领域、元数据与按状态编码

每个状态都归属于一个领域(domain),它「描述 App 的某项功能或某个区域」。一个领域同一时间只能持有一个活动状态,而不同的领域则能让多个状态同时进行中。1该场次的示例是一项 A/B 实验:开启实验性变更时,支出数据以小批次从数据库取出;关闭时则用较大批次。把标签页状态与批次大小状态放在不同的领域,意味着「MetricKit 会为每个标签页与每种批次大小各自交付指标」。1按画面的遥测与实验读数,出自同一条流程,且来自现场。

该场次的接入有三个步骤:导入 StateReporting 框架,创建一个领域(「通常是一个反向 DNS 字符串」)并在设置 MetricManager 实例时注册它,接着在 App 进入每个状态时报告转换,例如转换到以字符串「Reports」标识的状态。1若需更细的粒度,您可用 ReportableMetadata 宏定义自己的 struct,以该元数据类型创建一个 StateReporter,并在报告转换时同时带上标签与您的自定义类型。该场次的 ViewConfiguration 示例带有一个 listSize 值,以及列表是否已排序的信息。1再次提醒:该场次是在幻灯片上展示这套流程,并未提供完整签名,因此请把这个形式当成需在文档中确认的东西,而非可直接复制的语法。

在接收端,报告多出了第二个维度。在报告任何状态之前,您指标报告上的 stateEntries 属性是空的。接入之后,报告会带有 StateEntry 值,每一个都持有「在该单个状态所花时间内汇总的指标数值」。1对于服务器流程,您可以把编码输出按领域分组:在您 JSONEncoderuserInfo 属性上把 encodingFormatKey 键设为 byStateReportingDomain,编码后的报告便会把 state entries 与 interval entries 都「按报告中存在的每个领域与状态分组」呈现。1

最佳实践,以及从何着手

该场次以一段读来像是来之不易的结构设计建议作结。领域应狭隘地限定在 App 的单一区域。状态转换「应代表稳定且有意义的阶段,而非短暂的 UI 事件」。1请把每个状态设计成:当退步出现时,仅凭状态本身就能给您足够的信息去瞄准修复。同时,请克制把每件事都加上度量的冲动:「状态太多可能导致数据过于细碎,反而更难解读整体面貌」,而且状态数量设有上限,以将额外开销降到最低(该场次并未给出数字)。1在发布前,请用 Points of Interest 工具验证所报告的状态是否符合您的预期。1

采集端只是整个系统的一半。该场次直言「跨所有设备分析指标是一个数据科学问题」:您要搭起一台服务器来吸收报告,沿着您关心的维度汇总,建立基线,并监看两个方向上的变动。1Codable 报告与 byStateReportingDomain 编码的存在,正是为了喂养这条流程。

对于既有的采用者,结尾的指示很明确:「如果您正在使用 MXMetricManager API,请迁移到新的 MetricManager API,以充分利用所有这些新功能。」1该场次的每一项进展都活在这套新 API 中,而该场次把它们呈现为「框架的未来」。1

FAQ

iOS 27 的 MetricKit 究竟改了什么?

这个框架以现代 Swift 优先的 API 重建。入口是新的 MetricManager 类;指标与诊断报告以可 await 的异步流送达(metricReportsdiagnosticReports);报告符合 Codable,可直接做 JSON 编码;而其结构可在代码中通过 intervalEntries 与指标分组来浏览。iOS 27 还新增了 Metal 帧率指标、内存异常诊断、一个把崩溃诊断与指标计入串联起来的崩溃 category,以及用于按状态指标的 StateReporting 框架。1

StateReporting 如何判定哪些指标属于哪个状态?

您的 App 会报告转换:它正要进入的状态,落在您所定义的某个领域内。MetricKit 会追踪 App 停留在每个状态的时间长短,并把该段时间内的指标数值汇总起来。没有起止配对;App 只是在任一时刻报告它当下所处的状况。每个状态接着会在指标报告中各自取得一条 StateEntry1

我能不能同时追踪多个维度,例如画面与实验分组?

可以。每个领域同一时间能持有一个活动状态,但不同的领域是并行运行的。该场次的报销 App 把当前标签页放在一个领域,把数据库批次大小实验放在另一个领域,而 MetricKit 会为每个标签页与每种批次大小各自交付指标。1

我该把每个 UI 事件都当成状态报告吗?

不该。该场次建议采用代表稳定、有意义阶段的状态,而非短暂的 UI 事件;领域应狭隘地限定在 App 的单一区域;整体上也要有所节制:状态太多会让数据更难解读,而系统也对状态数量设有上限以将额外开销降到最低。发布前,请用 Points of Interest 工具验证您的状态。1

我一定得离开 MXMetricManager 吗?

该场次的建议是从 MXMetricManager 迁移到新的 MetricManager API,因为其中涵盖的每一项新功能(异步流、Codable 报告、状态感知指标、新的指标与诊断类型)都是这套新 API 独有的。1


今年 MetricKit 是一则由两部分组成的故事中属于现场的那一半:Instruments 在实验室里让您看见卡顿,而状态感知的 MetricKit 则告诉您对真实用户而言是哪些画面在卡——实验室那一侧的内容收录于 Instruments 27 与 App 响应性。真正能修好一个 71 ms/s 标签页的渲染工作,则谈于 iOS 27 中的 SwiftUI 性能与互通。至于为何混合后的平均值一开始就会误导人,正是 性能盲点 的主题。完整的系列主页为 Apple Ecosystem Series

References


  1. Apple, WWDC 2026 session 222, Meet the new MetricKit. Source for the iOS 27 ground-up rebuild and Swift-first API framing, the MetricManager entry point and the metricReports / diagnosticReports async streams, Codable reports and JSONEncoder usage, intervalEntries and metric groups (.cpu, .memory, .display, .gpu, peakMemory), the Metal frame rate metric, memory exception diagnostics, the crash termination category, the submitReport() backtrace walkthrough, the StateReporting framework (domains, transition model, StateReporter, ReportableMetadata, stateEntries, byStateReportingDomain via encodingFormatKey), the expense-app demo numbers (15 ms/s blended; 1 ms/s Spending tab versus 71 ms/s Reports tab), the state best practices and Points of Interest validation, and the guidance to migrate from MXMetricManager to MetricManager

相关文章

Instruments 27 在 App 响应性方面的新功能

Instruments 27 新增了 Top Functions、Run Comparisons、Swift executors 工具以及全新的 Inspector 面板,用于诊断 CPU、actor 与系统调用造成的卡顿。

4 分钟阅读

iOS 27 中的 SwiftUI 性能与互通

iOS 27 的 SwiftUI 如何处理 lazy stack 滚动、GPU 着色器效果以及与 AppKit/UIKit 的互通,内容取自 UI Frameworks 团队的三场 WWDC26 官方议程。

4 分钟阅读

从76到100:实现Lighthouse满分

一个个人作品集网站如何从移动端Lighthouse性能评分76分、CLS为0.493,提升到所有类别均达到100/100/100/100的满分。

3 分钟阅读