metrickit-diagnostics
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseMetricKit Diagnostics
MetricKit 诊断指南
Collect aggregated performance metrics and crash diagnostics from production
devices using MetricKit. The framework delivers daily metric payloads (CPU,
memory, launch time, hang rate, animation hitches, network usage) and
immediate diagnostic payloads (crashes, hangs, disk-write exceptions) with
full call-stack trees for triage.
使用MetricKit从生产环境设备收集聚合的性能指标与崩溃诊断信息。该框架每日推送指标载荷(包含CPU、内存、启动时间、卡顿率、动画掉帧、网络使用情况),并实时推送诊断载荷(包含崩溃、卡顿、磁盘写入异常),且附带完整调用栈树用于问题排查。
Contents
目录
Subscriber Setup
订阅者配置
Register a subscriber as early as possible — ideally in
or . MetricKit
starts accumulating reports after the first access to .
application(_:didFinishLaunchingWithOptions:)App.initMXMetricManager.sharedswift
import MetricKit
final class MetricsSubscriber: NSObject, MXMetricManagerSubscriber {
static let shared = MetricsSubscriber()
func subscribe() {
MXMetricManager.shared.add(self)
}
func unsubscribe() {
MXMetricManager.shared.remove(self)
}
func didReceive(_ payloads: [MXMetricPayload]) {
// Handle daily metrics
}
func didReceive(_ payloads: [MXDiagnosticPayload]) {
// Handle diagnostics (crashes, hangs, disk writes)
}
}尽早注册订阅者——理想情况下在或中完成。MetricKit在首次访问后开始累积报告。
application(_:didFinishLaunchingWithOptions:)App.initMXMetricManager.sharedswift
import MetricKit
final class MetricsSubscriber: NSObject, MXMetricManagerSubscriber {
static let shared = MetricsSubscriber()
func subscribe() {
MXMetricManager.shared.add(self)
}
func unsubscribe() {
MXMetricManager.shared.remove(self)
}
func didReceive(_ payloads: [MXMetricPayload]) {
// 处理每日指标
}
func didReceive(_ payloads: [MXDiagnosticPayload]) {
// 处理诊断信息(崩溃、卡顿、磁盘写入)
}
}UIKit Registration
UIKit 注册方式
swift
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
MetricsSubscriber.shared.subscribe()
return true
}swift
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
MetricsSubscriber.shared.subscribe()
return true
}SwiftUI Registration
SwiftUI 注册方式
swift
@main
struct MyApp: App {
init() {
MetricsSubscriber.shared.subscribe()
}
var body: some Scene {
WindowGroup { ContentView() }
}
}swift
@main
struct MyApp: App {
init() {
MetricsSubscriber.shared.subscribe()
}
var body: some Scene {
WindowGroup { ContentView() }
}
}Receiving Metric Payloads
接收指标载荷
MXMetricPayloadswift
func didReceive(_ payloads: [MXMetricPayload]) {
for payload in payloads {
let begin = payload.timeStampBegin
let end = payload.timeStampEnd
let version = payload.latestApplicationVersion
// Persist raw JSON before processing
let jsonData = payload.jsonRepresentation()
persistPayload(jsonData, from: begin, to: end)
processMetrics(payload)
}
}Availability: — iOS 13.0+, macOS 10.15+, visionOS 1.0+
MXMetricPayloadMXMetricPayloadswift
func didReceive(_ payloads: [MXMetricPayload]) {
for payload in payloads {
let begin = payload.timeStampBegin
let end = payload.timeStampEnd
let version = payload.latestApplicationVersion
// 处理前先持久化原始JSON
let jsonData = payload.jsonRepresentation()
persistPayload(jsonData, from: begin, to: end)
processMetrics(payload)
}
}兼容性: — iOS 13.0+, macOS 10.15+, visionOS 1.0+
MXMetricPayloadReceiving Diagnostic Payloads
接收诊断载荷
MXDiagnosticPayloadswift
func didReceive(_ payloads: [MXDiagnosticPayload]) {
for payload in payloads {
let jsonData = payload.jsonRepresentation()
persistPayload(jsonData)
if let crashes = payload.crashDiagnostics {
for crash in crashes {
handleCrash(crash)
}
}
if let hangs = payload.hangDiagnostics {
for hang in hangs {
handleHang(hang)
}
}
if let diskWrites = payload.diskWriteExceptionDiagnostics {
for diskWrite in diskWrites {
handleDiskWrite(diskWrite)
}
}
if let cpuExceptions = payload.cpuExceptionDiagnostics {
for cpuException in cpuExceptions {
handleCPUException(cpuException)
}
}
if let launchDiags = payload.appLaunchDiagnostics {
for launchDiag in launchDiags {
handleSlowLaunch(launchDiag)
}
}
}
}Availability: — iOS 14.0+, macOS 12.0+, visionOS 1.0+
MXDiagnosticPayloadMXDiagnosticPayloadswift
func didReceive(_ payloads: [MXDiagnosticPayload]) {
for payload in payloads {
let jsonData = payload.jsonRepresentation()
persistPayload(jsonData)
if let crashes = payload.crashDiagnostics {
for crash in crashes {
handleCrash(crash)
}
}
if let hangs = payload.hangDiagnostics {
for hang in hangs {
handleHang(hang)
}
}
if let diskWrites = payload.diskWriteExceptionDiagnostics {
for diskWrite in diskWrites {
handleDiskWrite(diskWrite)
}
}
if let cpuExceptions = payload.cpuExceptionDiagnostics {
for cpuException in cpuExceptions {
handleCPUException(cpuException)
}
}
if let launchDiags = payload.appLaunchDiagnostics {
for launchDiag in launchDiags {
handleSlowLaunch(launchDiag)
}
}
}
}兼容性: — iOS 14.0+, macOS 12.0+, visionOS 1.0+
MXDiagnosticPayloadKey Metrics
核心指标
Launch Time — MXAppLaunchMetric
启动时间 — MXAppLaunchMetric
swift
if let launch = payload.applicationLaunchMetrics {
let firstDraw = launch.histogrammedTimeToFirstDraw
let optimized = launch.histogrammedOptimizedTimeToFirstDraw
let resume = launch.histogrammedApplicationResumeTime
let extended = launch.histogrammedExtendedLaunch
}swift
if let launch = payload.applicationLaunchMetrics {
let firstDraw = launch.histogrammedTimeToFirstDraw
let optimized = launch.histogrammedOptimizedTimeToFirstDraw
let resume = launch.histogrammedApplicationResumeTime
let extended = launch.histogrammedExtendedLaunch
}Run Time — MXAppRunTimeMetric
运行时间 — MXAppRunTimeMetric
swift
if let runTime = payload.applicationTimeMetrics {
let fg = runTime.cumulativeForegroundTime // Measurement<UnitDuration>
let bg = runTime.cumulativeBackgroundTime
let bgAudio = runTime.cumulativeBackgroundAudioTime
let bgLocation = runTime.cumulativeBackgroundLocationTime
}swift
if let runTime = payload.applicationTimeMetrics {
let fg = runTime.cumulativeForegroundTime // Measurement<UnitDuration>
let bg = runTime.cumulativeBackgroundTime
let bgAudio = runTime.cumulativeBackgroundAudioTime
let bgLocation = runTime.cumulativeBackgroundLocationTime
}CPU, Memory, and Responsiveness
CPU、内存与响应性
swift
if let cpu = payload.cpuMetrics {
let cpuTime = cpu.cumulativeCPUTime // Measurement<UnitDuration>
}
if let memory = payload.memoryMetrics {
let peakMemory = memory.peakMemoryUsage // Measurement<UnitInformationStorage>
}
if let responsiveness = payload.applicationResponsivenessMetrics {
let hangTime = responsiveness.histogrammedApplicationHangTime
}
if let animation = payload.animationMetrics {
let scrollHitchRate = animation.scrollHitchTimeRatio // Measurement<Unit>
}swift
if let cpu = payload.cpuMetrics {
let cpuTime = cpu.cumulativeCPUTime // Measurement<UnitDuration>
}
if let memory = payload.memoryMetrics {
let peakMemory = memory.peakMemoryUsage // Measurement<UnitInformationStorage>
}
if let responsiveness = payload.applicationResponsivenessMetrics {
let hangTime = responsiveness.histogrammedApplicationHangTime
}
if let animation = payload.animationMetrics {
let scrollHitchRate = animation.scrollHitchTimeRatio // Measurement<Unit>
}Network and Cellular
网络与蜂窝数据
swift
if let network = payload.networkTransferMetrics {
let wifiUp = network.cumulativeWifiUpload // Measurement<UnitInformationStorage>
let wifiDown = network.cumulativeWifiDownload
let cellUp = network.cumulativeCellularUpload
let cellDown = network.cumulativeCellularDownload
}swift
if let network = payload.networkTransferMetrics {
let wifiUp = network.cumulativeWifiUpload // Measurement<UnitInformationStorage>
let wifiDown = network.cumulativeWifiDownload
let cellUp = network.cumulativeCellularUpload
let cellDown = network.cumulativeCellularDownload
}App Exit Metrics
应用退出指标
swift
if let exits = payload.applicationExitMetrics {
let fg = exits.foregroundExitData
let bg = exits.backgroundExitData
// Inspect normal, abnormal, watchdog, memory, etc.
}swift
if let exits = payload.applicationExitMetrics {
let fg = exits.foregroundExitData
let bg = exits.backgroundExitData
// 检查正常退出、异常退出、看门狗终止、内存不足等情况
}Call Stack Trees
调用栈树
MXCallStackTreejsonRepresentation()swift
func handleCrash(_ crash: MXCrashDiagnostic) {
let tree = crash.callStackTree
let treeJSON = tree.jsonRepresentation()
let exceptionType = crash.exceptionType
let signal = crash.signal
let reason = crash.terminationReason
uploadDiagnostic(
type: "crash",
exceptionType: exceptionType,
signal: signal,
reason: reason,
callStack: treeJSON
)
}
func handleHang(_ hang: MXHangDiagnostic) {
let tree = hang.callStackTree
let duration = hang.hangDuration // Measurement<UnitDuration>
uploadDiagnostic(type: "hang", duration: duration, callStack: tree.jsonRepresentation())
}The JSON structure contains an array of call stack frames with binary name,
offset, and address. Symbolicate using or upload dSYMs to your
analytics service.
atosAvailability: — iOS 14.0+, macOS 12.0+, visionOS 1.0+
MXCallStackTreeMXCallStackTreejsonRepresentation()swift
func handleCrash(_ crash: MXCrashDiagnostic) {
let tree = crash.callStackTree
let treeJSON = tree.jsonRepresentation()
let exceptionType = crash.exceptionType
let signal = crash.signal
let reason = crash.terminationReason
uploadDiagnostic(
type: "crash",
exceptionType: exceptionType,
signal: signal,
reason: reason,
callStack: treeJSON
)
}
func handleHang(_ hang: MXHangDiagnostic) {
let tree = hang.callStackTree
let duration = hang.hangDuration // Measurement<UnitDuration>
uploadDiagnostic(type: "hang", duration: duration, callStack: tree.jsonRepresentation())
}JSON结构包含一组调用栈帧,包含二进制名称、偏移量和地址。可使用工具或上传dSYM文件至分析服务进行符号化。
atos兼容性: — iOS 14.0+, macOS 12.0+, visionOS 1.0+
MXCallStackTreeCustom Signpost Metrics
自定义Signpost指标
Use with a MetricKit log handle to capture custom performance
intervals. These appear in the daily under .
mxSignpostMXMetricPayloadsignpostMetrics结合MetricKit日志句柄使用捕获自定义性能区间。这些指标会出现在每日的字段中。
mxSignpostMXMetricPayloadsignpostMetricsCreating a Log Handle
创建日志句柄
swift
let metricLog = MXMetricManager.makeLogHandle(category: "Networking")swift
let metricLog = MXMetricManager.makeLogHandle(category: "Networking")Emitting Signposts
发送Signpost
swift
import os
func fetchData() async throws -> Data {
let signpostID = MXSignpostIntervalData.makeSignpostID(log: metricLog)
mxSignpost(.begin, log: metricLog, name: "DataFetch", signpostID: signpostID)
let data = try await URLSession.shared.data(from: url).0
mxSignpost(.end, log: metricLog, name: "DataFetch", signpostID: signpostID)
return data
}swift
import os
func fetchData() async throws -> Data {
let signpostID = MXSignpostIntervalData.makeSignpostID(log: metricLog)
mxSignpost(.begin, log: metricLog, name: "DataFetch", signpostID: signpostID)
let data = try await URLSession.shared.data(from: url).0
mxSignpost(.end, log: metricLog, name: "DataFetch", signpostID: signpostID)
return data
}Reading Custom Metrics from Payload
从载荷中读取自定义指标
swift
if let signposts = payload.signpostMetrics {
for metric in signposts {
let name = metric.signpostName // "DataFetch"
let category = metric.signpostCategory // "Networking"
let count = metric.totalCount
if let intervalData = metric.signpostIntervalData {
let avgMemory = intervalData.averageMemory
let cumulativeCPUTime = intervalData.cumulativeCPUTime
}
}
}The system limits the number of custom signpost metrics per log to reduce on-device overhead. Reserve custom metrics for critical code paths.
swift
if let signposts = payload.signpostMetrics {
for metric in signposts {
let name = metric.signpostName // "DataFetch"
let category = metric.signpostCategory // "Networking"
let count = metric.totalCount
if let intervalData = metric.signpostIntervalData {
let avgMemory = intervalData.averageMemory
let cumulativeCPUTime = intervalData.cumulativeCPUTime
}
}
}系统会限制每个日志的自定义Signpost指标数量,以降低设备端开销。请仅为关键代码路径添加自定义指标。
Exporting and Uploading Payloads
载荷导出与上传
Both payload types conform to and provide
for easy serialization.
NSSecureCodingjsonRepresentation()swift
func persistPayload(_ jsonData: Data, from: Date? = nil, to: Date? = nil) {
let fileName = "metrics_\(ISO8601DateFormatter().string(from: Date())).json"
let url = FileManager.default.temporaryDirectory.appending(path: fileName)
try? jsonData.write(to: url)
}
func uploadPayloads(_ jsonData: Data) {
Task.detached(priority: .utility) {
var request = URLRequest(url: URL(string: "https://api.example.com/metrics")!)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = jsonData
_ = try? await URLSession.shared.data(for: request)
}
}两种载荷类型均遵循协议,并提供方法用于序列化。
NSSecureCodingjsonRepresentation()swift
func persistPayload(_ jsonData: Data, from: Date? = nil, to: Date? = nil) {
let fileName = "metrics_\(ISO8601DateFormatter().string(from: Date())).json"
let url = FileManager.default.temporaryDirectory.appending(path: fileName)
try? jsonData.write(to: url)
}
func uploadPayloads(_ jsonData: Data) {
Task.detached(priority: .utility) {
var request = URLRequest(url: URL(string: "https://api.example.com/metrics")!)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = jsonData
_ = try? await URLSession.shared.data(for: request)
}
}Retrieving Past Payloads
检索历史载荷
If the subscriber was not registered when payloads arrived, retrieve them
using and . These return reports
generated since the last allocation of the shared manager.
pastPayloadspastDiagnosticPayloadsswift
let pastMetrics = MXMetricManager.shared.pastPayloads
let pastDiags = MXMetricManager.shared.pastDiagnosticPayloads若注册订阅者时已有载荷推送,可使用和检索这些数据。它们会返回自共享管理器上次分配以来生成的报告。
pastPayloadspastDiagnosticPayloadsswift
let pastMetrics = MXMetricManager.shared.pastPayloads
let pastDiags = MXMetricManager.shared.pastDiagnosticPayloadsExtended Launch Measurement
扩展启动时间测量
Track post-first-draw setup work (loading databases, restoring state) as
part of the launch metric using extended launch measurement.
swift
let taskID = MXLaunchTaskID("com.example.app.loadDatabase")
MXMetricManager.shared.extendLaunchMeasurement(forTaskID: taskID)
// Perform extended launch work...
await database.load()
MXMetricManager.shared.finishExtendedLaunchMeasurement(forTaskID: taskID)Extended launch times appear under in
.
histogrammedExtendedLaunchMXAppLaunchMetric使用扩展启动时间测量功能,将首次绘制后的设置工作(如加载数据库、恢复状态)纳入启动指标。
swift
let taskID = MXLaunchTaskID("com.example.app.loadDatabase")
MXMetricManager.shared.extendLaunchMeasurement(forTaskID: taskID)
// 执行扩展启动工作...
await database.load()
MXMetricManager.shared.finishExtendedLaunchMeasurement(forTaskID: taskID)扩展启动时间会显示在的字段中。
MXAppLaunchMetrichistogrammedExtendedLaunchXcode Organizer Integration
Xcode Organizer集成
Xcode Organizer shows the same MetricKit data aggregated across all users
who have opted in to share diagnostics. Use Organizer for trend analysis:
- Metrics tab: Battery, performance, and disk-write metrics over time
- Regressions tab: Automatic detection of metric regressions per version
- Crashes tab: Crash logs with symbolicated stack traces
MetricKit on-device collection complements Organizer by letting you route
raw data to your own backend for custom dashboards, alerting, and filtering
by user cohort.
Xcode Organizer会展示所有选择共享诊断信息的用户的MetricKit聚合数据。可使用Organizer进行趋势分析:
- 指标标签页:电池、性能与磁盘写入指标的时间趋势
- 回归标签页:自动检测各版本的指标回归情况
- 崩溃标签页:带有符号化栈追踪的崩溃日志
设备端MetricKit收集的数据与Organizer互补,可将原始数据发送至自有后端,用于自定义仪表盘、告警及用户群体筛选。
Common Mistakes
常见错误
DON'T: Subscribe to MXMetricManager too late
错误做法:过晚订阅MXMetricManager
The system may deliver pending payloads shortly after launch. Subscribing
late (e.g., in a view controller) risks missing them entirely.
swift
// WRONG — subscribing in a view controller
override func viewDidLoad() {
super.viewDidLoad()
MXMetricManager.shared.add(self)
}
// CORRECT — subscribe in application(_:didFinishLaunchingWithOptions:)
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions opts: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
MXMetricManager.shared.add(metricsSubscriber)
return true
}系统可能在启动后不久推送待处理载荷。若过晚订阅(如在视图控制器中),可能会完全错过这些载荷。
swift
// 错误示例——在视图控制器中订阅
override func viewDidLoad() {
super.viewDidLoad()
MXMetricManager.shared.add(self)
}
// 正确示例——在application(_:didFinishLaunchingWithOptions:)中订阅
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions opts: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
MXMetricManager.shared.add(metricsSubscriber)
return true
}DON'T: Ignore MXDiagnosticPayload
错误做法:忽略MXDiagnosticPayload
Only handling means you miss crash, hang, and disk-write
diagnostics — the most actionable data MetricKit provides.
MXMetricPayloadswift
// WRONG — only implementing metric callback
func didReceive(_ payloads: [MXMetricPayload]) { /* ... */ }
// CORRECT — implement both callbacks
func didReceive(_ payloads: [MXMetricPayload]) { /* ... */ }
func didReceive(_ payloads: [MXDiagnosticPayload]) { /* ... */ }仅处理会错过崩溃、卡顿和磁盘写入诊断信息——这些是MetricKit提供的最具可操作性的数据。
MXMetricPayloadswift
// 错误示例——仅实现指标回调
func didReceive(_ payloads: [MXMetricPayload]) { /* ... */ }
// 正确示例——实现两个回调
func didReceive(_ payloads: [MXMetricPayload]) { /* ... */ }
func didReceive(_ payloads: [MXDiagnosticPayload]) { /* ... */ }DON'T: Process payloads without persisting first
错误做法:未持久化就处理载荷
The system delivers each payload once. If your subscriber crashes during
processing, the data is lost permanently.
swift
// WRONG — process inline, crash loses data
func didReceive(_ payloads: [MXDiagnosticPayload]) {
for p in payloads {
riskyProcessing(p) // If this crashes, payload is gone
}
}
// CORRECT — persist raw JSON first, then process
func didReceive(_ payloads: [MXDiagnosticPayload]) {
for p in payloads {
let json = p.jsonRepresentation()
try? json.write(to: localCacheURL()) // Safe on disk
Task.detached { self.processAsync(json) }
}
}系统仅推送每个载荷一次。若订阅者在处理过程中崩溃,数据会永久丢失。
swift
// 错误示例——直接处理,崩溃会丢失数据
func didReceive(_ payloads: [MXDiagnosticPayload]) {
for p in payloads {
riskyProcessing(p) // 若此处崩溃,载荷将丢失
}
}
// 正确示例——先持久化原始JSON,再处理
func didReceive(_ payloads: [MXDiagnosticPayload]) {
for p in payloads {
let json = p.jsonRepresentation()
try? json.write(to: localCacheURL()) // 安全存储到磁盘
Task.detached { self.processAsync(json) }
}
}DON'T: Do heavy work synchronously in didReceive
错误做法:在didReceive中同步执行繁重工作
The callback runs on an arbitrary thread. Blocking it with heavy processing
or synchronous network calls delays delivery of subsequent payloads.
swift
// WRONG — synchronous upload in callback
func didReceive(_ payloads: [MXMetricPayload]) {
for p in payloads {
let data = p.jsonRepresentation()
URLSession.shared.uploadTask(with: request, from: data).resume() // sync wait
}
}
// CORRECT — persist and dispatch async
func didReceive(_ payloads: [MXMetricPayload]) {
for p in payloads {
let json = p.jsonRepresentation()
persistLocally(json)
Task.detached(priority: .utility) {
await self.uploadToBackend(json)
}
}
}回调运行在任意线程。若在此处执行繁重处理或同步网络请求,会延迟后续载荷的推送。
swift
// 错误示例——在回调中同步上传
func didReceive(_ payloads: [MXMetricPayload]) {
for p in payloads {
let data = p.jsonRepresentation()
URLSession.shared.uploadTask(with: request, from: data).resume() // 同步等待
}
}
// 正确示例——持久化后异步分发
func didReceive(_ payloads: [MXMetricPayload]) {
for p in payloads {
let json = p.jsonRepresentation()
persistLocally(json)
Task.detached(priority: .utility) {
await self.uploadToBackend(json)
}
}
}DON'T: Expect immediate data in development
错误做法:在开发环境中期望立即获取数据
MetricKit aggregates data over 24-hour windows. Payloads do not arrive
immediately after instrumenting. Use Xcode Organizer or simulated payloads
for faster iteration during development.
MetricKit会在24小时窗口内聚合数据。 instrumentation后不会立即推送载荷。开发期间可使用Xcode Organizer或模拟载荷加快迭代速度。
Review Checklist
检查清单
- called in
MXMetricManager.shared.add(subscriber)orapplication(_:didFinishLaunchingWithOptions:)App.init - Subscriber conforms to and inherits
MXMetricManagerSubscriberNSObject - Both and
didReceive(_: [MXMetricPayload])implementeddidReceive(_: [MXDiagnosticPayload]) - Raw persisted to disk before processing
jsonRepresentation() - Heavy processing dispatched asynchronously off the callback thread
- JSON uploaded with dSYMs for symbolication
MXCallStackTree - Custom signpost metrics limited to critical code paths
- and
pastPayloadschecked on launch for missed deliveriespastDiagnosticPayloads - Extended launch tasks call both and
extendLaunchMeasurementfinishExtendedLaunchMeasurement - Analytics backend accepts and stores MetricKit JSON format
- Xcode Organizer reviewed for regression trends alongside on-device data
- 在或
application(_:didFinishLaunchingWithOptions:)中调用App.initMXMetricManager.shared.add(subscriber) - 订阅者遵循协议并继承自
MXMetricManagerSubscriberNSObject - 实现和
didReceive(_: [MXMetricPayload])两个方法didReceive(_: [MXDiagnosticPayload]) - 处理前先将原始持久化到磁盘
jsonRepresentation() - 将繁重处理异步分发到回调线程外执行
- 上传JSON时附带dSYM文件以支持符号化
MXCallStackTree - 仅为关键代码路径添加自定义Signpost指标
- 启动时检查和
pastPayloads以获取遗漏的推送数据pastDiagnosticPayloads - 扩展启动任务同时调用和
extendLaunchMeasurementfinishExtendedLaunchMeasurement - 分析后端支持接收并存储MetricKit JSON格式数据
- 结合设备端数据,通过Xcode Organizer检查回归趋势