metrickit

metrickit

熱門

當需要使用 MetricKit 收集或分析 iOS/iPadOS 上線環境(production)的效能遙測資料時使用,包含 iOS 27 MetricManager 的非同步數據或診斷報告、卡頓或當機排查、自訂 signpost、延伸啟動量測、持久化匯出,以及 iOS 26 MXMetricManager 相容性處理。

961星標
48分支
更新於 2026/7/31
SKILL.md
唯讀
名稱
metrickit
描述

當需要使用 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

在應用程式啟動時,建立並保留一個長生命週期的 MetricManager。為 metricReportsdiagnosticReports 各啟動恰好一個消費任務(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 遵循 CodableSendable 協定。它描述一段時間區間內的狀況:

  • 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 遵循 CodableSendable 協定。它包含 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 的診斷類型包含 CrashDiagnosticHangDiagnosticCPUExceptionDiagnosticDiskWriteExceptionDiagnosticAppLaunchDiagnostic 以及 MemoryExceptionDiagnostic。其中記憶體異常(memory-exception)為 iOS 27 新增功能。

實用欄位包括:

診斷類型 重要欄位
CrashDiagnostic callStackTree、例外類型/代碼/原因、訊號(signal)、虛擬記憶體區域、終止類別/原因
HangDiagnostic callStackTreehangDuration
CPUExceptionDiagnostic callStackTreetotalCPUTimetotalSampledTime
DiskWriteExceptionDiagnostic callStackTreetotalBytesWritten
AppLaunchDiagnostic callStackTreelaunchDuration
MemoryExceptionDiagnostic callStackTree

Key Metric Results

根據調查需求挑選精簡的遙測指標,而不是將所有結果全數匯出。優先考慮相關類別:回應能力與異常終止;執行階段、CPU、記憶體、網路與儲存空間;或是啟動時間、顯示/GPU 以及自訂區間。

按 App 版本與環境元資料進行聚合統計,比較分佈狀況而非單一數值,並將效能退化與版本發布進行關聯分析。切勿將每日聚合數據視為單一使用者操作的精確追蹤紀錄。

若要將 MetricResult 精確配對對映至解析器或儀表板,請參閱 Key Metric Result Catalog

Call Stack Trees

iOS 27 的 CallStackTree 取代了 MXCallStackTree。它遵循 CodableSendable 協定,並提供:

  • 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)的問題:

  1. 使用 JSONEncoder 對完整的 MetricReportDiagnosticReport 進行編碼。
  2. 以原子操作方式(atomically)將位元組排入 App 自有的持久化發送匣(durable outbox)中。
  3. 記錄報告類型、結構版本(schema version)、App 版本以及穩定的去重金鑰(deduplication key)。
  4. 唯有在排隊成功後,才確認本機處理完成。
  5. 後續搭配重試、指數退避(backoff)、分批(batching)與保留限制進行非同步上傳。
  6. 唯有在伺服器接收成功後,才將發送匣內的項目標記為已上傳。
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.sharedMXMetricManagerSubscriber 以及舊版的 payload 回呼。

舊版 API 仍是唯一提供 pastPayloadspastDiagnosticPayloads 的官方管道。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