Highlight
iOS 27 将 MetricKit 用 Swift 从头重写,引入全新的
MetricManager和StateReporting框架,让开发者可以把性能指标按 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 同时提供 metricReports 和 diagnosticReports 两个 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()是新的入口类,替代了旧版的MXMetricManagermetricReports是AsyncSequence,用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是全天的聚合数据- 每个条目里的
values按metricGroup分组,如.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 率或内存峰值异常,一目了然。入口是 StateReportingDomain 和 StateReporter.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
- Find and fix performance issues in your Metal game — Metal 帧率指标的具体使用场景和优化方法
- What’s new in Swift — MetricKit 新 API 大量使用了 Swift 的 AsyncSequence 和宏系统
- What’s new in SwiftUI — SwiftUI 应用的页面状态天然适合用 StateReporting 做性能拆分
- What’s new in Instruments — 用 Points of Interest Instrument 验证 StateReporting 上报的状态是否符合预期
- Analyze app performance with MetricKit — 更深入的性能分析方法和指标解读
评论
GitHub Issues · utterances