-
了解全新的 MetricKit
借助新工具,比以往更快地发现和修复性能问题。与我们一起探索 MetricKit 如何为你提供关键的性能指标和实用的诊断信息,助你精准发现你的 App 在哪些方面仍有改进空间。我们还将介绍如何使用 StateReporting 框架按 App 状态对 App 的指标和诊断信息进行交叉分析,借此全面掌握相关情况,以便进一步排查并优化你 App 的体验。
章节
- 0:01 - Introduction
- 4:07 - Metrics
- 7:13 - Diagnostics
- 10:03 - Context
资源
- Getting started with StateReporting
- Analyzing app performance with MetricKit
- Monitoring app performance with MetricKit
- Track performance by app state using MetricKit
- MetricKit
相关视频
WWDC26
-
搜索此视频…
嗨,我是 Yonni,我是 MetricKit 团队的一名工程师。
优秀的 app 和游戏会监控 并优化其性能 在真实的世界中,在真实的设备上。 MetricKit 是一个框架, 可以为您提供真实的洞察 帮助了解您 app 的体验质量。 在本次 session 中,我将首先 介绍 MetricKit, 包括 iOS 27 中的新功能。 然后,我将向您展示如何开始接收 您的第一份指标报告。 您的第一份诊断报告。 最后,我将探讨 如何获取更丰富的数据, 通过将性能问题与 app 中的特定区域关联起来。
我先从 框架概述开始。 优化 app 性能 是一个过程。 您从收集数据开始, 然后分析数据以发现问题。 对于每个问题, 您需要进行分类以找到根本原因, 加以修复,然后回到第一步 监控结果。 MetricKit 是该工作流程中 的数据收集部分。 该框架提供两种类型的数据: 指标和诊断。 指标让您了解 性能某个方面 总体上是在改善还是恶化, 而诊断则告诉您哪条代码路径 导致了性能问题。
在 iOS 27 中,该框架 已从头开始重建, 采用了内容丰富、 富有表现力的现代 Swift 优先 API。 全新的 MetricKit API 是框架的未来。 我今天要讨论的 所有进展 都是这组新 API 所独有的。 指标是 app 的 持续健康信号。 启动时间、卡顿和动画指标 可以告诉您 app 的响应速度 和流畅程度。 启动缓慢会让用户感到沮丧, 并导致他们离开您的 app, 而快速启动则能让用户 直接进入 app 的核心体验。
CPU、GPU、磁盘写入和网络传输 等资源消耗指标 可以告诉您 app 的工作负载 及其对设备健康的影响。
例如,MetricKit 启动时间, 如首次绘制时间指标, 提供了落在特定时间范围区间内的 启动次数直方图。 该图显示了 app 的启动耗时, 即一天中每次有人打开 app 所花费的时间。 大多数启动耗时在 510 到 540 毫秒之间,有少数异常值。 您还可以跟踪持续的性能状况, 通过从 MetricKit 提供的数据中 得出您自己的洞察。 例如, MetricKit 报告了该 app, 其总卡顿时间 平均为 3 秒, 而 app 使用时长为 30 分钟。 该信息可用于推算出 平均每小时卡顿率为 6 秒。 如果您在 所有设备上汇总此数据, 这可以为您提供一个可量化的信号, 反映 app 性能的趋势。
在 iOS 27 中,MetricKit 可以提供每个指标 作为 app 状态的函数。 例如,在测量具有多个选项卡的 app 中的卡顿时间时, MetricKit 可以提供此指标, 并与当前活动选项卡为 选项卡 1、2 或 3 时的情况相交叉。 我稍后会详细介绍。 在 iOS 27 中,MetricKit 现在还 提供了一项新指标——Metal 帧率。 帧率是一个关键指标, 供游戏开发者 了解渲染性能。 要了解更多关于为平台优化游戏的信息, 请查看 session "查找并修复 Metal 游戏中的性能问题"。 除指标外, MetricKit 还提供诊断。 诊断包含有用的信息, 帮助您确定 哪条代码路径导致了 性能问题, 从而进行调查和修复。
在 iOS 27 中,MetricKit 提供内存异常诊断。 因此,当您的 app 或扩展因超出内存限制 而被终止时, 您可以更深入地了解发生了什么。
我将深入探讨您的 app 如何获取性能指标。 当用户一天中使用您的 app 时, MetricKit 会持续收集指标, 例如 app 启动、卡顿、内存和 CPU, 并在每日报告中 将其传递给您的 app。 在此报告中,MetricKit 提供一个 涵盖 app 全天使用情况的条目。 它还提供单独的条目 用于较小的时间段划分, 通常每个时间段几小时。 仅当存在与之关联的指标时, 才会显示这些较小的时间段划分。 以下是数据的结构。 在每个时间段内, 指标被组织成指标组。 每个组代表 系统的一个方面, 例如 .cpu、.memory、 .display 和 .gpu。 在一个组内,您将找到 各个性能指标。
我将在代码中演示这一过程。 您的入口点 是 MetricManager 类。 要接收报告,您需要通过 metricReports 属性来 await 它们。 此设置应在 app 启动时完成, 以避免因延迟订阅 而造成任何数据丢失。 MetricManager 应保持活跃状态, 以便数据流可以在后续数据就绪时 继续传递报告。 仅需这几行代码,您的 app 现在就能接收结构化的指标数据了。 通常,您可能希望 将这些指标发送到服务器, 以便您可以检查 app 在众多设备上的健康状况。 MetricReports 符合 Codable 协议, 这使得编码变得简单, 可以编码成 JSON 等格式 发送到服务器。 只需创建一个 JSONEncoder 并对整个报告进行编码即可。
如果您只想访问特定的 指标组或特定值, 您也可以进一步检查 指标报告。 为此,请遍历 您的 intervalEntries。 这包括一个全天聚合条目, 以及在可用时 较小的时间段窗口。 然后,将指标筛选为 您感兴趣的组。 在这种情况下,memoryMetrics 仅包含 内存组中的指标。 最后,对指标 case 进行 switch, 以访问您感兴趣的指标类型, 以及该报告中 该指标的值。 在这种情况下,代码仅处理 peakMemory 值。 在 app 启动后,立即在分离的任务 或专用服务类中执行此工作。
回到工作流程。 您现在已经完成了 收集阶段。 准备好进入分析阶段了。
分析所有设备上的指标 是一个数据科学问题。 为了实现这一分析, 您需要搭建一台服务器, 该服务器能够接收每一份报告, 并根据您关心的维度 对其进行聚合。 您需要决定 最佳的统计分析方法是什么, 以便生成您想要的数据, 并找到您希望挖掘的洞察。 使用您的自定义聚合, 这可以为您提供一条基准线, 了解您的 app 当前的性能状况。 然后,监控您的聚合指标, 以检测事情 何时在好转或恶化。
我已展示了如何使用指标 识别 app 中的问题。 现在,我将介绍如何使用诊断 来修复这些问题。
指标是 监控 app 的好方法。 当您持续收集指标 并监控性能时, 您可以进入工作流程的 分类阶段。 诊断在这一阶段 尤其有用。
当出现问题时, 例如崩溃或卡顿, 系统会在设备上 捕获诊断信息。 诊断报告会 打包详细信息, 并通过 MetricKit 立即传递给您的 app。 在其中,您将获得 有用的信息来分类问题。 例如, 许多诊断包含回溯信息, 显示事件发生时 确切的调用堆栈。
最重要的诊断之一 是崩溃诊断。 崩溃诊断 不仅提供回溯信息, 还会说明 您的 app 为何被终止, 以及一个异常类型, 说明这是哪种类型的失败。
在 iOS 27 中,终止类别 现在会说明每次崩溃 在指标中是如何被计入的。 这样, 如果异常终止呈上升趋势, 您可以将这些情况直接 与各个诊断关联起来。 在这个例子中,符号化的回溯信息 从线程启动时的系统中开始。 随着执行向下流转, 它进入了 app 的代码。 要找到崩溃位置, 您可以一路向下追踪调用。 在这里,执行到达 app 的 submitReport() 函数并停止。 这表明这是 执行路径中的失败点。
现在,您可以利用这些信息 来针对此函数进行修复。 要获取诊断报告, 您需要在 MetricManager 实例的 diagnosticReports 上进行 await。 与 MetricReports 一样,在 app 启动后 尽快开始监听此数据流, 在分离的任务 或专用服务类中进行。
与 MetricReports 一样, DiagnosticReports 也符合 Codable 协议。 这段代码 await 传入的 diagnosticReports, 然后使用 JSONEncoder 将其编码为 JSON。 现在,您可以将所有诊断信息 发送到您的分析服务器。
诊断报告也是结构化的, 因此您可以选择 您想接收的内容。 例如, 这段代码 await diagnosticReports, 并对不同类型的诊断 case 进行 switch。 在崩溃诊断的 case 中, 它提取回溯信息、 原因和类别。 现在,这些信息 可以由 app 处理, 例如将其上传到服务器。 在卡顿诊断的 case 中, 它可以使用 hang case 对该报告进行不同的处理。
我已介绍了如何获取 app 的 指标和诊断报告。 接下来,我将向您展示 如何将这些数据情境化。 到目前为止,MetricKit 提供的指标和诊断, 代表了 app 性能的 整体图景。 但是,要调查具体问题, 您可能需要更丰富、更细粒度的数据, 以告诉您更多 关于 app 状态的信息, 例如用户流程是什么 或 app 是如何配置的。 MetricKit 可以为您提供情境化数据, 将其关联到您为 app 定义的有意义的信息上。 以下是一个例子。 我有一个费用报告 app, 允许员工扫描收据、 提交费用,并按类别和预算 跟踪其支出。 这些功能被组织在 "报告"选项卡和"支出"选项卡中。
我对滚动卡顿指标感兴趣。 它表明 在一天的使用过程中, app 的总卡顿时间 为 4.5 秒, 滚动时长为 5 分钟。 这是每秒 15 毫秒的 卡顿率。 但这是所有 app 使用过程中 的平均滚动卡顿率, 即使用户在"报告"选项卡和 "支出"选项卡之间来回切换。 要了解收集该指标时 app 所处的条件, 您可以通过 StateReporting 框架 上报 app 状态。 我现在来探索一下。
状态是您定义的信息, 用于描述 app 的配置 或行为,以便 MetricKit 可以将指标 聚合为这些特征的函数。 当用户使用您的 app 时, 参数可能会根据 他们的使用方式而改变。
在费用 app 中,用户可能 在使用过程中在选项卡之间切换。 他们可能在"报告"选项卡上 添加新费用,然后离开 app, 再回到"支出"选项卡, 查看 他们的每日餐饮预算。 当 app 在这些状态之间 切换时, 它会上报这些切换。 然后,MetricKit 可以将其与 指标和诊断数据进行交叉。
您还可以为每个状态 添加更多详细信息, 通过添加自定义结构化类型。 对于费用 app,这可以是 关于视图中项目的详情, 例如交易列表是否被视为 小型、中型还是大型列表, 以及该列表上的交易 是否已排序。
现在,不再是获取所有这些状态下 的单一混合指标, 例如总滚动卡顿率 15 ms/s, 指标会针对 每个单独的状态上报。 在费用 app 中, 每个选项卡都有各自的指标。 在这个例子中,在"支出"选项卡上滚动 非常流畅, 卡顿率仅为 1 ms/s。 但是,在"报告"选项卡上 滚动时, 卡顿率飙升至 71 ms/s。 有了这种粒度,您可以得出 更有针对性的结论: "支出"选项卡表现出色! 但"报告"选项卡 正在经历严重的卡顿中断, 这正是您应该 集中优化精力的地方。
您提供的每个状态 都属于某个域。 域描述了 app 的某个功能或区域。 一个域在任意给定时刻 只能有一个活动状态。 独立的域允许多个状态 同时处于活动中。 在费用 app 中,我正在测试 一项实验性变更, 我想知道 它是否有助于提升性能。 开启实验后, 费用从数据库以 小批量方式获取。 关闭后, 则改用较大的批量。 通过将选项卡状态 和批量大小状态 放在独立的域中, MetricKit 将为每个选项卡 和每个批量大小提供独立的指标。
状态遵循转换模型。 您的 app 上报它要进入的状态, MetricKit 则跟踪 其在该状态中保持的时长。 没有开始或结束的配对—— app 上报它当前所处的条件, 在任意给定时刻。
要在 app 中上报状态,首先 导入 StateReporting 框架。 然后,创建一个域—— 通常是一个反向 DNS 字符串—— 并在设置 MetricManager 实例时 注册它。 最后,在 app 进入您定义的状态时 上报切换。 在这种情况下,app 切换到一个状态, 该状态由字符串 "Reports" 标识。 如果您想进一步 细化数据, 您可以为这些状态提供 额外的结构化信息, 通过使用 ReportableMetadata 宏 定义您自己的 struct。 然后,用这个元数据类型 创建一个新的 StateReporter。 最后,通过包含标签和您的自定义类型 来上报切换。 此示例 切换到 "Reports" 状态, 并且还提供了 一个 ViewConfiguration struct, 其中包含 listSize 的值 以及该列表上的项目是否已排序。
在向 app 添加状态之前, 您的指标报告会提供 涵盖 app 所有使用情况的宏观指标。 stateEntries 属性 包含状态感知指标。 当没有上报状态时, 此属性为空。 添加状态后,您的 MetricReport 现在将包含 StateEntry 值。 状态条目提供了另一种 感知 app 指标的方式。 每个状态都有其自己的 StateEntry, 其指标值聚合自 在该单独状态中花费的时间。 当您准备好将 此数据发送到分析服务器时, 您可以选择按状态上报域 对 MetricReport 数据进行分组。 配置您的 JSONEncoder 以按每个域对状态条目进行分组。 在编码器的 userInfo 属性上 设置键 encodingFormatKey 为值 byStateReportingDomain。 现在,当报告被编码时, 状态条目和时间段条目, 都将包含 您 app 的性能指标, 按报告中存在的 每个域和状态分组。
以下是定义状态时 需要牢记的一些最佳实践。
域应范围狭窄, 以便每个 app 区域都有其状态, 并能够理解 这些状态的数据。
状态切换应代表 稳定、有意义的阶段, 而非短暂的 UI 事件。 仔细考虑每个状态的含义, 以便当出现回归时, 该状态能提供 足够的信息来定位修复目标。 仔细规划 app 中 状态切换的数量, 以及您计划如何解读 每个状态和域生成的数据。 过多的状态可能导致 数据过于细粒度, 实际上反而难以 理解整体情况。 状态数量也有上限, 以将开销降至最低。 最后, 使用 Points of Interest 仪器 验证您上报的状态在发布前 符合您的预期。 MetricKit 让您比以往 更快地发现和修复性能问题。 持续监控数据, 以定向规划您的性能工作。
使用 MetricManager 开始收集 性能指标, 监控 app 的健康状况。
分析诊断以识别 具体的改进机会。 通过上报 app 的重要状态, 将您获取的数据情境化。 探索 MetricKit 提供的 新型数据, 例如内存诊断 和 Metal 帧率指标。 最后,如果您正在使用 MXMetricManager API, 请迁移到 新的 MetricManager API, 以充分利用 所有这些新功能。 感谢观看,祝您有一个精彩的 WWDC!
-
-
4:59 - Receive metrics from MetricKit
// Receive metrics from MetricKit import MetricKit let manager = MetricManager() for await report in manager.metricReports { processReport(report) } -
5:25 - Send your metrics to the server
// Send your metrics to the server import MetricKit for await report in manager.metricReports { let jsonData = try JSONEncoder().encode(report) sendToServer(jsonData) } -
5:44 - Access your performance metrics
// Access your performance metrics 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 } } } } -
8:59 - Receive diagnostics
// Receive diagnostics import MetricKit let manager = MetricManager() for await report in manager.diagnosticReports { processReport(report) } -
9:14 - Send your diagnostic data to the server
// Send your diagnostic data to the server import MetricKit for await report in manager.diagnosticReports { let jsonData = try JSONEncoder().encode(report) sendToServer(jsonData) } -
9:39 - Access your diagnostic data
// Access your diagnostic data 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 } } -
13:57 - Receive MetricKit data with states
// Receive MetricKit data with states import MetricKit import StateReporting let domain = StateReportingDomain("com.metrickitsample.tabs") let manager = MetricManager(enabledStateReportingDomains: [domain]) // Report transitions throughout the app let reporter = StateReporter.reporter(for: domain.rawValue) reporter.reportTransition(to: "Reports") -
14:21 - Define custom structured types
// Define custom structured types 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) ) -
15:29 - Send encoded metric reports to the server
// Send encoded metric reports to the server 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) }
-
-
- 0:01 - Introduction
MetricKit is a framework that provides metrics and diagnostics for monitoring real-world app performance. In iOS 27, the framework has been rebuilt from the ground up with a new Swift-first API and new features including Metal frame rate metrics, memory exception diagnostics, and granular data with state reporting.
- 4:07 - Metrics
MetricKit delivers daily reports containing performance metrics — including launch time, hangs, CPU, memory, organized into interval entries and metric groups. Metrics can be retrieved as Codable reports for server-side aggregation or inspected directly by filtering for specific groups and values.
- 7:13 - Diagnostics
When an app encounters a crash, hang, or other failure, MetricKit captures and immediately delivers a diagnostic report containing a symbolicated backtrace and metadata such as exception type and termination reason. iOS 27 adds memory exception diagnostics and a new termination category field on crash diagnostics.
- 10:03 - Context
The State Reporting framework lets apps report their active configuration or user flow as named domains and states, which then allows MetricKit to aggregate data separately for each state rather than blending them. Custom structured metadata can be attached to states using the @ReportableMetadata macro, and per-state metrics are surfaced as StateEntry values in the metric report.