关于 metrickit
面向开发者的 MetricKit 生产遥测指南,涵盖现代报告序列、持久化接收、诊断、自定义指标及 iOS 26 兼容路径。
主要功能
- 01指标与诊断报告序列
- 02持久化报告并延后上传
- 03崩溃、卡顿与资源诊断
- 04自定义 signpost 区间指标
- 05扩展启动任务测量
- 06iOS 26 兼容方案
适用场景
- 01接收生产环境性能报告
- 02在分析或上传前保存报告
- 03排查崩溃、卡顿和资源问题
- 04测量应用启动关键任务
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ dpearson2699/ swift-ios-skills --skill metrickit安装后在 Claude Code 中输入/metrickit确认已加载。
附带文件evals/evals.jsonreferences/metrickit-patterns.md
详细介绍
MetricKit
使用 MetricKit 获取开销低的生产环境遥测数据,以补充本地 Instruments 和 Xcode Organizer 分析。在 iOS 和 iPadOS 27 上,优先使用 Swift 优先的 MetricManager 报告序列。仅在明确的 iOS 26 兼容分支中保留 MXMetricManager。
Beta 敏感: 以下 iOS/iPadOS 27 接口基于 Apple 当前的 Beta 文档。由于此环境中没有 Xcode 27,因此尚未在本地通过编译验证。发布前请重新检查链接的 Apple 文档,并使用正式版 Xcode 27 SDK 编译。
实现持久化数据摄取、详细报告分析或 iOS 26 兼容路径时,请加载 MetricKit 扩展与兼容模式。
目录
- MetricManager 设置
- 接收指标报告
- 接收诊断报告
- 关键指标结果
- 调用栈树
- 自定义 Signpost 指标
- 持久化导出与上传
- 扩展启动测量
- iOS 26 兼容性
- Xcode Organizer
- 范围边界
- 常见错误
- 审查清单
- 参考资料
MetricManager 设置
应用启动时,创建并保留一个长生命周期的 MetricManager。为 metricReports 和 diagnosticReports 各启动一个消费者任务,且每个只能启动一个。
这两个属性都提供不抛错的 AsyncSequence 值:
metricReports: some AsyncSequence<MetricReport, Never>diagnosticReports: some AsyncSequence<DiagnosticReport, Never>
Apple 指出,同一序列的并发消费者可能收到不确定的子集。只有在单个消费者收到并持久化存储报告后,才进行分发;延迟订阅可能导致报告遗漏。
import MetricKit
@available(iOS 27.0, *)
final class MetricsService {
private let manager = MetricManager()
private var metricTask: Task<Void, Never>?
private var diagnosticTask: Task<Void, Never>?
func start(
persistMetric: @escaping @Sendable (MetricReport) async -> Void,
persistDiagnostic: @escaping @Sendable (DiagnosticReport) async -> Void
) {
guard metricTask == nil, diagnosticTask == nil else { return }
let manager = manager
metricTask = Task {
for await report in manager.metricReports {
await persistMetric(report)
}
}
diagnosticTask = Task {
for await report in manager.diagnosticReports {
await persistDiagnostic(report)
}
}
}
deinit {
metricTask?.cancel()
diagnosticTask?.cancel()
}
}
持久化闭包需根据应用实际情况实现。请使用下文的先持久化工作流,而不是丢弃报告、仅记录日志或直接逐条上传。如果需要按状态划分的指标,请使用文档所述的 init(enabledStateReportingDomains:) 初始化方法和必需的域来构造管理器。
接收指标报告
MetricReport 遵循 Codable 和 Sendable。它通过以下内容描述一个时间间隔:
timeRange: DateInterval- 可选的
environment元数据 - 用于全天及更短时间间隔测量的
intervalEntries - 与应用状态相关联的测量数据
stateEntries
指标报告通常按日送达。提取单项结果之前,请先持久化完整报告。
进行每日分析时,请读取文档所述的 fullDayEntry,并对其 MetricResult 值进行 switch 处理:
let entry = report.intervalEntries.fullDayEntry
for result in entry.values {
switch result {
case .hangTime(let metric):
analyzeHangTime(metric)
case .peakMemory(let metric):
analyzePeakMemory(metric)
case .timeToFirstDraw(let metric):
analyzeLaunch(metric)
case .signpostInterval(let metric):
analyzeSignpost(metric)
@unknown default:
preserveUnknownMetric(result)
}
}
使用 @unknown default,以免 Beta 版或未来的结果导致数据摄取流水线变得脆弱。即使当前应用无法识别某个结果,也要保留报告的原始编码数据。
接收诊断报告
DiagnosticReport 遵循 Codable 和 Sendable。它包含一个 timeRange、必需的 environment 元数据,以及一个 DiagnosticResult。
诊断是独立的事件型报告,旨在由 MetricKit 生成后立即交付给提示词。不要假设每次崩溃、卡顿或资源事件都会生成报告;仍需考虑系统采样和资格条件。
持久化存储后,明确路由结果:
switch report.result {
case .crash(let diagnostic):
analyzeCrash(diagnostic)
case .hang(let diagnostic):
analyzeHang(diagnostic)
case .cpuException(let diagnostic):
analyzeCPUException(diagnostic)
case .diskWriteException(let diagnostic):
analyzeDiskWrites(diagnostic)
case .appLaunch(let diagnostic):
analyzeLaunch(diagnostic)
case .memoryException(let diagnostic):
analyzeMemory(diagnostic)
@unknown default:
preserveUnknownDiagnostic(report)
}
iOS/iPadOS 27 的诊断类型包括 CrashDiagnostic、HangDiagnostic、CPUExceptionDiagnostic、DiskWriteExceptionDiagnostic、AppLaunchDiagnostic 和 MemoryExceptionDiagnostic。内存异常类型是 iOS 27 新增的。
有用的字段包括:
| 诊断类型 | 重要字段 |
|---|---|
CrashDiagnostic | callStackTree、异常类型/代码/原因、信号、虚拟内存区域、终止类别/原因 |
HangDiagnostic | callStackTree、hangDuration |
CPUExceptionDiagnostic | callStackTree、totalCPUTime、totalSampledTime |
DiskWriteExceptionDiagnostic | callStackTree、totalBytesWritten |
AppLaunchDiagnostic | callStackTree、launchDuration |
MemoryExceptionDiagnostic | callStackTree |
关键指标结果
选择一组精简的遥测词汇,以匹配调查需求,而不是导出所有结果。优先关注相关类别:响应能力与终止;运行时、CPU、内存、网络和存储;或启动、显示/GPU 和自定义时间间隔。
按应用版本和环境元数据聚合,比较分布而非单个值,并将回归问题与版本发布相关联。避免把每日聚合数据当作某个用户操作的精确轨迹。
在将确切的 MetricResult case 映射到解析器或仪表板时,请加载关键指标结果目录。
调用栈树
iOS 27 的 CallStackTree 取代了 MXCallStackTree。它遵循 Codable 和 Sendable,并提供:
- 用于遍历帧的
forEachFrame - 用于按线程分析的
callStackThreads - 用于映像和二进制元数据的
binaryInfo
在展开或符号化之前,先存储完整的诊断报告。保留二进制标识符和偏移量,以便服务端符号化使用匹配的归档文件和 dSYM。
自定义 Signpost 指标
通过管理器创建 OS 日志,然后使用 mxSignpost 测量开始/结束时间:
let log = manager.logHandle(category: "ImagePipeline")
mxSignpost(.begin, log: log, name: "Decode")
await decodeImage()
mxSignpost(.end, log: log, name: "Decode")
MetricKit 将聚合结果公开为 MetricResult.signpostInterval。不要在 MetricReport 上查找 signpostMetrics 数组。
需要 MetricKit 资源测量时,请使用 mxSignpost。Apple 文档指出,使用 OSSignposter 和 MetricKit 日志句柄创建的时间间隔,不会填充 mxSignpost 提供的资源测量字段。
将高频本地追踪与用于 MetricKit 聚合的少量稳定生产时间间隔分开。
持久化导出与上传
将报告交付视为至少一次的摄取问题:
- 使用
JSONEncoder对完整的MetricReport或DiagnosticReport进行编码。 - 将字节原子性地加入应用自有的持久化发件箱队列。
- 记录报告类型、架构版本、应用版本和稳定的去重键。
- 仅在成功入队后确认本地处理完成。
- 稍后通过重试、退避、批处理和保留限制进行上传。
- 仅在服务端接受后,将发件箱项标记为已上传。
let data = try JSONEncoder().encode(report)
try await durableOutbox.enqueue(data, kind: .metric)
durableOutbox 是应用自有的抽象,并非 MetricKit API。不要在序列消费者中同步执行网络上传。
通过现代序列接收报告时,持久化本地存储是恢复机制;不要让成功摄取依赖于假定存在的现代回补 API。
扩展启动测量
对于超出首次绘制时间的工作,请在管理器上使用 trackLaunchTask(id:onTrackingError:_:):
await manager.trackLaunchTask(
id: "bootstrap-data",
onTrackingError: { error in
recordLaunchTrackingError(error)
}
) {
await bootstrapApplication()
}
此 API 为 @MainActor,并提供同步和异步重载。任务闭包的结果和抛出的错误会传递给调用方;MetricManager.LaunchTaskError 会通过 onTrackingError 报告,且不会中断受跟踪的工作。结果会显示为 MetricResult.extendedLaunch。
使用稳定的 LaunchTaskID 值,并且只跟踪启动关键工作。
iOS 26 兼容性
对于仍支持部署到 iOS 26 的应用,请使用可用性边界:
- iOS/iPadOS 27:使用
MetricManager并消费两个异步报告序列。 - iOS/iPadOS 26 及更早的受支持版本:使用
MXMetricManager.shared、MXMetricManagerSubscriber和旧版载荷回调。
旧版 API 仍是唯一有文档说明支持 pastPayloads 和 pastDiagnosticPayloads 的分支。MXMetricManager 在 iOS 27 中已弃用,因此应通过可用性检查将其隔离,而不是把旧版载荷混入现代处理管线。
加载 iOS 26 兼容性,了解完整的订阅器、旧版 signpost、历史载荷和扩展启动模式。
Xcode Organizer
在构建自定义后端之前,使用 Xcode Organizer 检查 Apple 汇总的生产指标、卡顿、崩溃和回归。如果产品需要自定义关联、保留、告警或与现有可观测性系统集成,请直接接入 MetricKit 数据。
不要指望开发设备上的运行能够重现生产报告的人群范围、频率或汇总方式。
范围边界
| 任务 | 改用 |
|---|---|
| 在本地重现问题或记录详细跟踪信息 | debugging-instruments |
| 诊断保留对象或内存图路径 | ios-memgraph-analysis |
| 调整 SwiftUI 失效、标识或滚动代码 | swiftui-performance |
| 研究结构化的 EnergyKit 影响数据 | energykit |
| 设计通用日志记录和 os_signpost 策略 | swift-logging |
MetricKit 用于识别生产环境中的症状和趋势。请将实际代码修复交由负责相关子系统的技能处理。
常见错误
| 错误 | 修正方式 |
|---|---|
| 在序列循环中直接上传 | 先在本地编码并加入队列;稍后再异步上传。 |
| 认为每个诊断事件都会生成报告 | 将诊断视为经采样、由系统生成的证据。 |
在 MetricManager 上查找 pastPayloads | 仅在 iOS 26 的 MXMetricManager 分支中保留旧版回填逻辑。 |
| 只解析已知的枚举情况 | 保留原始报告并使用 @unknown default。 |
在 iOS 27 分支中使用 MXMetricManager.makeLogHandle | 使用 manager.logHandle(category:)。 |
| 在 iOS 27 分支中使用成对的启动扩展/结束调用 | 使用 trackLaunchTask。 |
| 将 MetricKit 汇总数据视为本地跟踪信息 | 使用 Instruments 和 signpost 重现问题。 |
审查清单
- iOS 27 分支在启动时运行,保留一个
MetricManager,并为每个报告序列配置一个消费者。 - 每份报告都会先编码并可靠地加入队列,然后再进行分析或上传。
- 诊断和指标的 switch 都使用
@unknown default。 - 未知的原始报告仍可恢复。
- 保留符号化元数据和匹配的 dSYM。
- 自定义区间使用
manager.logHandle(category:)和mxSignpost。 - 扩展启动工作使用
trackLaunchTask。 - iOS 26 的
MXMetricManager路径通过可用性检查隔离。 - 适用时,生产汇总数据会引导开展本地 Instruments 调查。
- 已重新检查 iOS 27 beta API,并使用发布版 Xcode 27 SDK 编译。