适用于使用 MetricKit 收集或分析 iOS/iPadOS 生产环境性能遥测数据的场景。涵盖 iOS 27 MetricManager 异步指标与诊断报告、卡顿/崩溃排查、自定义 Signpost 埋点、延长启动测量、持久化导出以及 iOS 26 MXMetricManager 兼容处理等。
MetricKit
使用 MetricKit 进行低开销的生产环境遥测,作为本地 Instruments 和 Xcode Organizer 分析的有力补充。在 iOS 与 iPadOS 27 上,推荐优先使用 Swift 原生的 MetricManager 报告异步序列。仅在明确需要向下兼容 iOS 26 的分支中保留 MXMetricManager。
Beta 版本注意事项: 以下针对 iOS/iPadOS 27 的 API 说明基于 Apple 当前的 Beta 版文档。由于当前环境暂无 Xcode 27,未在本地进行编译器验证。发布前请务必重新核对 Apple 最新文档,并使用正式版 Xcode 27 SDK 进行编译。
在实现持久化采集、详细报告分析或 iOS 26 兼容路径时,请查阅 MetricKit 扩展与兼容模式。
目录
- MetricManager 配置
- 接收指标报告
- 接收诊断报告
- 核心性能指标
- 调用栈树
- 自定义 Signpost 性能埋点
- 持久化导出与上传
- 延长启动测量
- iOS 26 兼容处理
- Xcode Organizer
- 适用边界
- 常见误区
- 审查清单
- 参考资料
MetricManager 配置
在 App 启动时,创建并持有单例/长生命周期的 MetricManager 实例。为 metricReports 和 diagnosticReports 分别启动有且仅有一个消费任务(Consumer Task)。
这两个属性均暴露了非抛出型 AsyncSequence:
metricReports: some AsyncSequence<MetricReport, Never>diagnosticReports: some AsyncSequence<DiagnosticReport, Never>
Apple 官方文档指出,如果多个消费者并发监听同一个序列,每个消费者可能只会收到部分不确定的数据碎片。因此,请确保在单个消费者收到并持久化保存报告后再进行数据分发(Fan-out);延迟订阅可能会导致报告丢失。
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()
}
}
持久化闭包由应用业务自行实现。请采用下文推荐的“持久化优先”工作流,切勿直接丢弃、仅打日志或收到报告就直接同步发起上传。如果需要特定状态维度的指标,可在构造 MetricManager 时传入官方文档指定的 init(enabledStateReportingDomains:) 构造器及所需的领域(Domains)。
接收指标报告
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 版或未来新增的指标类型导致解析流水线崩溃或失效。即使当前 App 版本尚不理解某个指标结果,也应完整保留原始编码后的报告数据。
接收诊断报告
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、异常类型/代码/原因、Signal 信号、虚拟内存区域、终止分类/原因 |
HangDiagnostic |
callStackTree、hangDuration |
CPUExceptionDiagnostic |
callStackTree、totalCPUTime、totalSampledTime |
DiskWriteExceptionDiagnostic |
callStackTree、totalBytesWritten |
AppLaunchDiagnostic |
callStackTree、launchDuration |
MemoryExceptionDiagnostic |
callStackTree |
核心性能指标
建议根据排查目标挑选一套精简的遥测指标集,而不是盲目导出所有数据。优先聚焦关键维度:响应度与异常终止;运行时、CPU、内存、网络与存储;启动耗时、渲染/GPU 以及自定义 Signpost 间隔。
按 App 版本和环境元数据进行聚合统计,比较数据分布而非单一数值,并将性能劣货(Regression)与具体发布版本相关联。避免将按天聚合的数据误当作单次用户操作的精准 Trace 追溯。
若需要将具体的 MetricResult 枚举映射到解析器或大盘,请查阅 核心指标字典。
调用栈树
iOS 27 中的 CallStackTree 替代了旧版的 MXCallStackTree。它实现了 Codable 和 Sendable,并暴露了以下接口:
forEachFrame:用于遍历调用栈帧callStackThreads:用于基于线程维度分析binaryInfo:用于提取镜像(Image)与二进制元数据
在扁平化解析或符号化处理之前,请先完整保存原始诊断报告。保留二进制标识符与偏移量(Offsets),以便服务端符号化时可以使用匹配的归档文件和 dSYM 进行解析。
自定义 Signpost 性能埋点
通过 MetricManager 创建 OS Log 句柄,然后使用 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 log 句柄创建的区间,不会填充 mxSignpost 所提供的资源测量字段。
建议将高频的本地 Trace 跟踪与用于 MetricKit 聚合的高稳定性生产环境区间打点区分开。
持久化导出与上传
应将报告交付视为“至少一次(At-least-once)”交付问题:
- 使用
JSONEncoder对完整的MetricReport或DiagnosticReport进行序列化。 - 将字节数据原子化地入队到应用自建的本地持久化 Outbox(发件箱)中。
- 记录报告类型、Schema 版本、App 版本以及稳定的去重 Key。
- 仅在入队成功后,才确认本地消费成功(Acknowledge)。
- 后续后台异步上传,支持失败重试、退避策略(Backoff)、批处理以及存储上限控制。
- 仅在服务器成功接收响应后,才将 Outbox 中的对应记录标记为已上传。
let data = try JSONEncoder().encode(report)
try await durableOutbox.enqueue(data, kind: .metric)
durableOutbox 是应用层自建的抽象,并非 MetricKit 提供的原生 API。切勿在 Sequence 消费循环内部同步发起网络上传。
本地持久化存储是现代 Sequence 接口接收报告的核心容灾机制;不要将采集成功率寄希望于假设存在的现代补发(Backfill)API 上。
延长启动测量
对于延伸至首帧渲染(Time to First Draw)之后的初始化工作,请使用管理器上的 trackLaunchTask(id:onTrackingError:_:) 进行追踪:
await manager.trackLaunchTask(
id: "bootstrap-data",
onTrackingError: { error in
recordLaunchTrackingError(error)
}
) {
await bootstrapApplication()
}
该 API 标记为 @MainActor,提供了同步与异步重载版本。Task 闭包的返回值和抛出的错误会直接传递给调用方;若发生 MetricManager.LaunchTaskError,将通过 onTrackingError 回调通知,不会中断正在被追踪的业务逻辑。测量结果会体现为 MetricResult.extendedLaunch。
使用稳定的 LaunchTaskID,且仅追踪对启动至关重要的关键任务。
iOS 26 兼容处理
对于仍需支持 iOS 26 的 App,请使用可用性检查(Availability Boundary)进行分支隔离:
- iOS/iPadOS 27:使用
MetricManager并监听两个异步报告序列(Async Sequence)。 - iOS/iPadOS 26 及更早支持的版本:使用
MXMetricManager.shared、MXMetricManagerSubscriber以及旧版的 Payload 回调。
旧版 API 是目前官方文档中唯一支持 pastPayloads 和 pastDiagnosticPayloads 的分支。由于 MXMetricManager 在 iOS 27 中已弃用,请将其隔离在可用性检查分支之后,切勿将旧版 Payload 混合写入现代化的处理流水线中。
查阅 iOS 26 兼容处理 了解完整的 Subscriber、旧版 Signpost、历史 Payload 提取以及延长启动测量的模式说明。
Xcode Organizer
在搭建自定义数据后台之前,可以先利用 Xcode Organizer 查看 Apple 官方聚合的生产环境指标、卡顿、崩溃及性能劣货分析。当产品需要自定义数据关联、留存分析、告警通知或与已有可观测性(Observability)系统打通时,再采用 MetricKit 直接采集。
请勿期望在开发设备或测试机上的运行效果能够还原生产环境报告的样本规模、推送频次或聚合结果。
适用边界
| 目标场景 | 推荐替代工具/方案 |
|---|---|
| 在本地复现问题或记录详细 Trace | debugging-instruments |
| 诊断强引用/未释放对象或内存图路径 | ios-memgraph-analysis |
| 调优 SwiftUI 刷新失效、Identity 标识或滚动性能 | swiftui-performance |
| 深入研究 EnergyKit 结构化能耗影响数据 | energykit |
| 规划通用日志记录与 os_signpost 策略 | swift-logging |
MetricKit 侧重于发现生产环境中的症状与趋势。具体的代码修复工作,请对接负责相应子系统的 Skill。
常见误区
| 常见错误 | 正确做法 |
|---|---|
| 在 Sequence 消费循环内部直接发起网络上传 | 先在本地进行 JSON 序列化并入队持久化 Outbox,后续再异步上传。 |
| 假定每一个诊断事件都会生成一份报告 | 将诊断报告视为经过系统采样和规则筛选后的数据证据。 |
在 MetricManager 上寻找 pastPayloads |
(后略) |
<!-- truncated for translation batch; full body continues in source -->






