當需要使用 MetricKit 收集或分析 iOS/iPadOS 上線環境(production)的效能遙測資料時使用,包含 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 Extended and Compatibility Patterns。
Contents
- MetricManager Setup
- Receiving Metric Reports
- Receiving Diagnostic Reports
- Key Metric Results
- Call Stack Trees
- Custom Signpost Metrics
- Durable Export and Upload
- Extended Launch Measurement
- iOS 26 Compatibility
- Xcode Organizer
- Scope Boundaries
- Common Mistakes
- Review Checklist
- References
MetricManager Setup
在應用程式啟動時,建立並保留一個長生命週期的 MetricManager。為 metricReports 和 diagnosticReports 各啟動恰好一個消費任務(consumer task)。
這兩個屬性皆暴露拋出錯誤外的 AsyncSequence(nonthrowing 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()
}
}
持久化閉包(closures)取決於具體應用程式。請採用下方說明的「持久化優先」工作流程實作,切勿直接丟棄、僅記錄日誌或收到報告後直接發起網路上傳。若需要按狀態劃分數據指標,請使用官方提供的 init(enabledStateReportingDomains:) 建構子並傳入所需 Domain。
Receiving Metric Reports
MetricReport 遵循 Codable 與 Sendable 協定。它描述一段時間區間內的狀況:
timeRange: DateInterval- 可選的
environment中元資料(metadata) - 用於整天或更短區間量測的
intervalEntries - 用於與應用程式狀態關聯之量測的
stateEntries
數據報告通常以一天為週期送達。在擷取個別指標結果前,請務必先將完整的報告持久化儲存。
進行每日分析時,請讀取官方提供的 fullDayEntry 並針對其 MetricResult 進行模式配對:
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 尚未支援解析某個指標結果,也應保留原始編碼後的報告。
Receiving Diagnostic Reports
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。其中記憶體異常(memory-exception)為 iOS 27 新增功能。
實用欄位包括:
| 診斷類型 | 重要欄位 |
|---|---|
CrashDiagnostic |
callStackTree、例外類型/代碼/原因、訊號(signal)、虛擬記憶體區域、終止類別/原因 |
HangDiagnostic |
callStackTree、hangDuration |
CPUExceptionDiagnostic |
callStackTree、totalCPUTime、totalSampledTime |
DiskWriteExceptionDiagnostic |
callStackTree、totalBytesWritten |
AppLaunchDiagnostic |
callStackTree、launchDuration |
MemoryExceptionDiagnostic |
callStackTree |
Key Metric Results
根據調查需求挑選精簡的遙測指標,而不是將所有結果全數匯出。優先考慮相關類別:回應能力與異常終止;執行階段、CPU、記憶體、網路與儲存空間;或是啟動時間、顯示/GPU 以及自訂區間。
按 App 版本與環境元資料進行聚合統計,比較分佈狀況而非單一數值,並將效能退化與版本發布進行關聯分析。切勿將每日聚合數據視為單一使用者操作的精確追蹤紀錄。
若要將 MetricResult 精確配對對映至解析器或儀表板,請參閱 Key Metric Result Catalog。
Call Stack Trees
iOS 27 的 CallStackTree 取代了 MXCallStackTree。它遵循 Codable 與 Sendable 協定,並提供:
forEachFrame:用於走訪框架(frame traversal)callStackThreads:用於執行緒導向的分析binaryInfo:用於映像檔(image)與二進位檔元資料
在進行打平(flattening)或符號化(symbolication)之前,請先完整儲存整份診斷報告。請妥善保留二進位檔識別碼與偏移量(offsets),以便伺服器端符號化時能使用匹配的封檔(archives)與 dSYM 檔案。
Custom Signpost Metrics
透過 manager 建立 OS log,然後使用 mxSignpost 進行 begin/end 的時間區間量測:
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 handle 建立的區間,不會填入 mxSignpost 所提供的資源量測欄位。
請將高頻率的本機追蹤(local tracing)與用於 MetricKit 聚合分析的少數穩定上線區間分開處理。
Durable Export and Upload
請將報告交付視為「至少一次交付」(at-least-once ingestion)的問題:
- 使用
JSONEncoder對完整的MetricReport或DiagnosticReport進行編碼。 - 以原子操作方式(atomically)將位元組排入 App 自有的持久化發送匣(durable outbox)中。
- 記錄報告類型、結構版本(schema version)、App 版本以及穩定的去重金鑰(deduplication key)。
- 唯有在排隊成功後,才確認本機處理完成。
- 後續搭配重試、指數退避(backoff)、分批(batching)與保留限制進行非同步上傳。
- 唯有在伺服器接收成功後,才將發送匣內的項目標記為已上傳。
let data = try JSONEncoder().encode(report)
try await durableOutbox.enqueue(data, kind: .metric)
durableOutbox 是應用程式自訂的抽象層,並非 MetricKit 的 API。切勿在序列消費者迴圈中執行同步網路上傳。
本機持久化儲存是透過新型非同步序列接收報告時的復原機制;請勿將成功導入建立在「假設存在新版補載 API(backfill API)」的前提上。
Extended Launch Measurement
對於延伸超出「首次繪製時間(time to first draw)」的工作,請使用 manager 上的 trackLaunchTask(id:onTrackingError:_:):
await manager.trackLaunchTask(
id: "bootstrap-data",
onTrackingError: { error in
recordLaunchTrackingError(error)
}
) {
await bootstrapApplication()
}
此 API 為 @MainActor 標註,並提供同步與非同步的過載(overloads)。Task 閉包的回傳值與拋出的錯誤會傳遞給呼叫者;而 MetricManager.LaunchTaskError 則會透過 onTrackingError 報告,不會中斷被追蹤的工作。量測結果會以 MetricResult.extendedLaunch 形式呈現。
請使用固定的 LaunchTaskID 數值,且僅追蹤對啟動至關重要的工作。
iOS 26 Compatibility
若 App 仍支援 iOS 26,請使用版本可用性檢查的分支(availability boundary):
- iOS/iPadOS 27:使用
MetricManager並消費兩個非同步報告序列。 - iOS/iPadOS 26 及更早的支援版本:使用
MXMetricManager.shared、MXMetricManagerSubscriber以及舊版的 payload 回呼。
舊版 API 仍是唯一提供 pastPayloads 與 pastDiagnosticPayloads 的官方管道。MXMetricManager 已在 iOS 27 棄用(deprecated),因此請透過可用性檢查將其隔離,切勿將舊版 payload 混入新型的處理管道中。
有關完整的訂閱者、舊版 signpost、歷史 payload 以及延伸啟動模式,請參閱 iOS 26 Compatibility。
Xcode Organizer
在建置自訂後端之前,可使用 Xcode Organizer 檢視 Apple 聚合的上線數據指標、卡頓、當機與效能退化。只有在產品需要自訂關聯分析、資料保留、警報或整合現有可觀測性系統(observability system)時,才需要直接使用 MetricKit 導入。
切勿期望開發裝置上的執行結果能反映生產環境報告的母體規模、發生頻率或聚合情況。
Scope Boundaries
| 任務 | 請改用 |
|---|---|
| 在本機重現問題或記錄詳細追蹤 | debugging-instruments |
| 診斷未釋放物件(retained objects)或記憶體圖路徑 | ios-memgraph-analysis |
| 調校 SwiftUI 無效化(invalidation)、標識(identity)或滾動程式碼 | swiftui-performance |
| 研究 EnergyKit 結構化能耗影響數據 | energykit |
| 設計通用日誌與 os_signpost 策略 | swift-logging |
MetricKit 用於識別生產環境中的症狀與趨勢。請將實際的程式碼修復工作轉交給負責該子系統的 skill。
Common Mistakes
| 錯誤 | 正確做法 |
|---|---|
| 在序列迴圈中直接進行上傳 | 先在開頭完成本機編碼與排隊,後續再非同步上傳。 |
| 假設每次診斷事件都會產生報告 | 將診斷報告視為經由系統抽樣產出的證據。 |
在 MetricManager 上尋找 pastPayloads |
僅在 iOS 26 及更早版本的 MXMetricManager 處理路徑中使用 pastPayloads。 |




