MetricKit 重构:iOS 27 中的状态感知遥测
苹果在 WWDC 2026 上演示的应用,报告其滚动卡顿率为每秒 15 毫秒——这是按一整天使用情况平均得出的数字。可一旦按标签页拆分,同样的数据却讲述了截然不同的故事:一个标签页是 1 ms/s,另一个则是 71 ms/s。1 一个界面几乎完美无瑕;另一个用会上的话来说,正”经历严重的中断”。1 混合后的那个数字把这两个事实都掩盖了。Session 222”Meet the new MetricKit”讲述的,正是 iOS 27 如何弥合这一差距:对该框架 API 表面的彻底重构,以及一个全新的配套框架 StateReporting,它把整应用层面的现场指标转化为按状态划分的指标。现场遥测终于能回答每位性能工程师最先问出的那个问题:到底是哪个界面慢?
TL;DR
- 在 iOS 27 中,MetricKit”已从头彻底重构,配备了语境丰富、表达力强的现代 Swift 优先 API”,会上介绍的每一项新能力都仅限于这套新 API。1
- 入口是
MetricManager类。应用在启动时 awaitmetricReports和diagnosticReports这两个异步流,且两种报告类型都遵循Codable,因此用一个JSONEncoder即可将它们直接发送到您的分析服务器。1 - 报告是结构化的:
intervalEntries包含一条覆盖一整天的条目以及更小粒度的细分,按.cpu、.memory、.display、.gpu等指标组组织,最终细化到peakMemory这样的单个值。1 - iOS 27 的新数据:用于衡量渲染性能的 Metal 帧率指标、针对超出内存上限被终止的内存异常诊断,以及一个把单条崩溃诊断与指标趋势关联起来的崩溃
category。1 - 重头戏是
StateReporting框架:报告应用所处的状态(当前标签页、实验分组、视图配置),MetricKit 便按状态聚合指标,用按界面划分的细分取代那一个混合数字。1
从头彻底重构
MetricKit 团队的工程师 Yonni 从 1:23 开始介绍 iOS 27 的这次重构。
MetricKit 的职责并未改变:它是性能工作流中”负责收集的那一环”,提供两类数据。指标告诉您某一性能领域总体上是在改善还是恶化;诊断则告诉您是哪条代码路径导致了问题。1 改变的是您接收这些数据的方式——一切都变了。会上说得很直白:在 iOS 27 中,该框架”已从头彻底重构,配备了语境丰富、表达力强的现代 Swift 优先 API”,而且”我今天要讲的所有改进都仅限于这套新 API”。1
新的入口是 MetricManager 类。您无需再注册 delegate 并解析 payload,而是通过 metricReports 属性以异步流的形式 await 报告。会上给出了两条直接的操作准则:在应用启动时完成设置,”以避免因订阅延迟而丢失任何数据”;并让 MetricManager 保持存活,”以便后续数据就绪时,这些流能持续投递报告”。1 苹果建议在应用一启动就在一个 detached task 或专门的 service 类中运行这部分工作。1
会上是在幻灯片中展示代码的,因此下面的代码片段只是与其描述相符的示意性调用形态;上线前请对照苹果文档核实确切的签名。
// 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),再 switch 各个指标 case,即可取到 peakMemory 这样的单个值。1
iOS 27 中指标目录也有所扩充。除了启动耗时直方图(会上的示例显示大多数启动落在 510 到 540 毫秒之间)、卡顿、动画指标,以及 CPU、GPU、磁盘写入、网络传输等资源消耗之外,MetricKit 还新增了 Metal 帧率指标。会上称帧率是”游戏开发者理解渲染性能的一项关键指标”,并就优化方面推荐了”Find and fix performance issues in your Metal game”。1
请依赖 MetricKit 的启动指标,而不要自己去插桩测量启动。苹果测量启动耗时,是从用户点按应用图标的那一刻起,到首帧绘制为止——而这早于您的进程真正存在。3 自行编写的计时器只能在您的代码开始运行后才启动,因此它完全错过了 main 之前的那段窗口,也就低估了用户实际感受到的启动耗时。请去读取 MetricKit 提供的那份直方图,而不是去搭一个看不见关键部分的计时器。
诊断:回溯、内存异常与崩溃类别
指标告诉您某处出现了退化;诊断则告诉您退化在哪里。当出现问题时,比如崩溃或卡顿,”系统会在设备上捕获一份诊断”,而诊断报告会”把细节打包好,并通过 MetricKit 立即投递给您的应用”。1 许多诊断都包含回溯,展示事件发生时确切的调用栈。在会上的演示中,符号化后的回溯从系统代码中的线程起点开始,进入应用,止于应用的 submitReport() 函数——它标出了失败点,也指明了修复应针对的位置。1
崩溃诊断携带回溯、终止原因和异常类型。iOS 27 新增了一个终止 category,它”标明每次崩溃在指标中是如何被计入的”,因此”如果异常终止呈上升趋势,您可以直接把它们与单条诊断关联起来”。1 您仪表盘上的那条指标线,与它背后的单条崩溃报告,终于共享了一个关联键。
iOS 27 还新增了内存异常诊断:”当您的应用或扩展因超出内存上限而被终止时,您能获得更多关于发生了什么的洞察。”1 扩展被明确纳入范围,这对于任何要远程排查小组件或扩展内存被杀问题的人都很重要。
消费端与指标端如出一辙。您在自己的 MetricManager 实例上 await diagnosticReports,同样是在应用启动时于 detached task 或 service 类中进行,而 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:从一个混合数字到按界面划分的真相
上面的一切讲的仍是整应用层面的遥测,而整应用层面的遥测有其天花板。会上那个报销应用把问题讲得很具体。该应用把功能组织为一个 Reports 标签页和一个 Spending 标签页。一天下来,MetricKit 报告说在 5 分钟的滚动中共有 4.5 秒的卡顿时长:即 15 ms/s 的卡顿率。但这个数字是”覆盖全部应用使用情况的平均滚动卡顿率,哪怕用户是在 Reports 标签页和 Spending 标签页之间来回切换”。1 您知道应用有卡顿,却不知道卡在哪里。
全新的 StateReporting 框架消除了这种混合。状态是”您所定义的、用以描述应用配置或行为的信息,从而让 MetricKit 能够把指标按这些特征聚合”。1 当用户在标签页之间移动时,应用会报告每一次切换,而 MetricKit 会把这些状态与指标和诊断数据求交集。1
演示中带来回报的那一刻,正是这次重构的理由所在。指标不再是那一个混合的 15 ms/s 数字,而是按状态送达:Spending 标签页滚动起来”极其顺滑”,仅 1 ms/s,而 Reports 标签页则”飙升到 71 ms/s”。1 会上得出了那个混合数字永远无法支撑的结论:”Spending 标签页表现很棒!但 Reports 标签页正经历严重的中断,而那恰恰是您的优化精力应当聚焦之处。”1 一个数字变成了一份判决书,也变成了一张工单。
状态遵循一种切换模型,而非成对包裹。”没有起止配对——应用只是在任意给定时刻报告它当前所处的状况”,而 MetricKit 会追踪应用停留在每个状态中的时长。1
域、元数据,以及按状态编码
每个状态都被归入某个域,而域”描述应用的一项功能或一个区域”。一个域同一时刻只能持有一个活跃状态,而设置多个相互独立的域,则可让多个状态同时处于进行中。1 会上的示例是一次 A/B 实验:在实验性改动开启时,从数据库以小批量获取报销条目;关闭时则用更大的批量。把标签页状态与批量大小状态放进彼此独立的域,意味着”MetricKit 会为每个标签页、每种批量大小分别投递指标”。1 按界面划分的遥测与实验读数,出自同一条流水线,且来自现场。
会上把接入分为三步:导入 StateReporting 框架;创建一个域(”通常是一个反向 DNS 字符串”)并在设置 MetricManager 实例时注册它;然后在应用进入各个状态时报告切换,比如切换到由字符串”Reports”标识的状态。1 若想要更细的粒度,您可以用 ReportableMetadata 宏定义自己的 struct,用该元数据类型创建一个 StateReporter,并在报告切换时同时带上标签和您的自定义类型。会上的 ViewConfiguration 示例携带了一个 listSize 值,以及列表是否已排序的信息。1 再次提醒:会上是在幻灯片中展示这一流程的,并未给出完整签名,因此请把这些形态当作需要在文档中核实的内容,而非可以照抄的语法。
在接收端,报告新增了第二个维度。在尚未报告任何状态之前,您指标报告上的 stateEntries 属性是空的。接入之后,报告便携带一组 StateEntry 值,每个值持有”在该单个状态停留期间聚合得出的指标值”。1 对于服务器流水线,您可以把编码后的输出按域分组:将您 JSONEncoder 的 userInfo 属性上的 encodingFormatKey 键设为 byStateReportingDomain,编码后的报告便会”按报告中存在的每个域和状态分组”地同时呈现状态条目与区间条目。1
最佳实践,以及从何入手
会上以一番听来像是历经磨砺得来的 schema 设计建议作结。域应当被狭窄地限定在应用的单一区域。状态切换”应当代表稳定、有意义的阶段,而非转瞬即逝的 UI 事件”。1 设计每个状态时要做到:当退化出现时,仅凭这个状态就能给出足够信息去针对性地修复。同时也要克制住给一切都插桩的冲动:”状态过多会导致数据过于细碎,反而让人更难看清整体图景”,而且为了把开销降到最低,状态数量设有上限(会上未给出具体数字)。1 上线前,请用 Points of Interest 工具验证报告出的状态与您的预期相符。1
基数在元数据这一侧也是同样的陷阱。请把快速变化的状态值归入粗粒度的类别(小、中、大),而不要报告精确计数。在苹果 WWDC 2026 的性能实验室里,团队现场回答了关于 MetricKit 接入与更广泛功耗工作流的问题(记录于 苹果性能团队在 WWDC26 实验室所说的内容),他们指出记录”1,000 与 1,001 个条目”只增加成本却不带来洞察:这两个值落在同一性能区间,因此为每一个都设一个独立状态,买到的只有开销,别无其他。4 请挑选那些会改变行为的边界,把边界之间的一切都折叠起来。
收集只是整个系统的一半。会上直言”跨全部设备分析指标是一个数据科学问题”:您需要搭起一个服务器来摄取报告,沿着您关心的维度进行聚合,建立基线,并监控两个方向上的任何变动。1 Codable 报告与 byStateReportingDomain 编码的存在,正是为了喂养这样一条流水线。
对于既有的接入者,结尾的指示十分明确:”如果您正在使用 MXMetricManager API,请迁移到新的 MetricManager API,以用上这些全新能力。”1 苹果的文档现已正式确认这一迁移:MXMetricManager 自 27.0 起被标记为弃用,并附有指引”Use MetricManager instead”。2 本周期内,同样以 27 为界、分阶段强制实施的做法也出现在别处,包括 Image Playground 的 iOS 27 中 ImageCreator 的弃用,在那里弃用警告会在公开发布时转为硬性中断。会上的每一项进展都活在这套新 API 中,而会上也把它们称为”该框架的未来”。1
FAQ
iOS 27 中 MetricKit 究竟变了什么?
该框架已用现代 Swift 优先 API 重构。入口是全新的 MetricManager 类;指标报告与诊断报告以可 await 的异步流(metricReports、diagnosticReports)形式送达;报告遵循 Codable,可直接进行 JSON 编码;其结构也可在代码中通过 intervalEntries 和指标组导航。iOS 27 还新增了 Metal 帧率指标、内存异常诊断、一个把崩溃诊断与指标计入方式关联起来的崩溃 category,以及用于按状态划分指标的 StateReporting 框架。1
StateReporting 如何决定哪些指标归属于哪个状态?
您的应用报告切换:它正在进入的状态,以及您所定义的某个域。MetricKit 追踪应用停留在每个状态中的时长,并把这段时间内的指标值聚合起来。这里没有起止配对;应用只是在任意给定时刻报告它当前所处的状况。每个状态随后都会在指标报告中获得属于自己的 StateEntry。1
我能否同时追踪多个维度,比如界面和实验分组?
可以。每个域同一时刻只能持有一个活跃状态,但相互独立的域可以并发运行。会上那个报销应用把当前标签页放进一个域,把数据库批量大小实验放进另一个域,MetricKit 便为每个标签页、每种批量大小分别投递指标。1
我应该把每一个 UI 事件都报告为一个状态吗?
不应该。会上建议状态应代表稳定、有意义的阶段,而非转瞬即逝的 UI 事件;域应被狭窄地限定在应用的单一区域;总体上也要克制:状态过多会让数据更难解读,而且系统为了把开销降到最低,对状态数量设有上限。上线前请用 Points of Interest 工具验证您的状态。1
我必须放弃 MXMetricManager 吗?
会上的指引是从 MXMetricManager 迁移到新的 MetricManager API,因为所涵盖的每一项新能力(异步流、Codable 报告、状态感知指标、新的指标与诊断类型)都仅限于这套新 API。1
MetricKit 是今年这个两段式故事的现场那一半:Instruments 在实验室里把卡顿展示给您看,而状态感知的 MetricKit 则告诉您真实用户在哪些界面遭遇卡顿——实验室那一侧的内容见 Instruments 27 与应用响应性。真正能修复一个 71 ms/s 标签页的渲染工作,见 iOS 27 中的 SwiftUI 性能与互操作。而混合平均值之所以一开始就会误导人,正是 性能盲点 探讨的主题。整个系列的中心枢纽是 Apple 生态系统系列。
参考资料
-
Apple, WWDC 2026 session 222, Meet the new MetricKit. Source for the iOS 27 ground-up rebuild and Swift-first API framing, the
MetricManagerentry point and themetricReports/diagnosticReportsasync streams,Codablereports andJSONEncoderusage,intervalEntriesand metric groups (.cpu,.memory,.display,.gpu,peakMemory), the Metal frame rate metric, memory exception diagnostics, the crash terminationcategory, thesubmitReport()backtrace walkthrough, theStateReportingframework (domains, transition model,StateReporter,ReportableMetadata,stateEntries,byStateReportingDomainviaencodingFormatKey), 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 fromMXMetricManagertoMetricManager. ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩ -
Apple Developer Documentation:
MXMetricManager. Marked deprecated as of 27.0, with the guidance “Use MetricManager instead.” ↩ -
Apple Developer Documentation: Reducing your app’s launch time. Launch time is measured from the moment the user taps the app icon to the first frame drawn, before the app’s process exists. ↩
-
Apple, WWDC 2026 performance group lab, session 8003. Paraphrased from a locally transcribed recording; no official transcript is published. The team advised bucketing fast-changing state metadata into coarse categories, noting that recording “1,000 versus 1,001 items” adds cost without insight. ↩