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

  1. 使用 JSONEncoder 對完整的 MetricReport 或 DiagnosticReport 進行編碼。
  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.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。