In iOS 27, MetricKit has been rebuilt from the ground up with a modern Swift-first API featuring async/await streams, structured metric groups, state-aware reporting via the StateReporting framework, and new diagnostics including memory exceptions and Metal frame rate metrics.
โข Entire API rebuilt as a Swift-first async/await interface replacing the MXMetricManagerSubscriber delegate pattern
โข MetricReports now deliver per-state breakdowns via the StateReporting framework integration, replacing flat aggregated-only data
โข New Metal frame rate metric added for game developers
โข New memory exception diagnostics surface why an app or extension was terminated for exceeding memory limits
โข Crash diagnostics now include a termination category correlating crashes to abnormal termination metrics
โข Async streams replace delegate callbacks, making it trivial to await metric and diagnostic reports at app launch with no boilerplate
โข State-aware metrics let you intersect performance data (hangs, hitches, CPU) with your own app states (active tab, feature flags), turning blended averages into actionable per-feature insights
โข New Metal frame rate metric and memory exception diagnostics give game developers and all apps deeper termination and render-performance visibility
Demonstrates the new async/await MetricKit API to receive daily metric reports and immediate diagnostic reports at app launch, extracting peak memory and crash backtrace information.
โimport UIKit+import SwiftUIimport MetricKitโ// MARK: - Old delegate-based MetricKit (pre-iOS 27)โclass LegacyPerformanceMonitor: NSObject, MXMetricManagerSubscriber {โ static let shared = LegacyPerformanceMonitor()โ private let manager = MXMetricManager.shared+// MARK: - Metric collection service+actor PerformanceMonitor {+ static let shared = PerformanceMonitor()+ private let manager = MetricManager()โ func start() {โ manager.add(self)+ func startMonitoring() async {+ async let _ = collectMetrics()+ async let _ = collectDiagnostics()}โ // Called once per day with an array of payloadsโ func didReceive(_ payloads: [MXMetricPayload]) {โ for payload in payloads {โ // Encode entire payload to JSONโ print("[MetricKit] Payload: \(payload.jsonRepresentation())")+ private func collectMetrics() async {+ for await report in manager.metricReports {+ // MetricReports are Codable โ send to your analytics server+ if let data = try? JSONEncoder().encode(report),+ let json = String(data: data, encoding: .utf8) {+ print("[MetricKit] Report JSON: \(json.prefix(200))")+ }โ // Access memory metrics directly off the payloadโ if let mem = payload.memoryMetrics {โ print("[MetricKit] Peak memory: \(mem.peakMemoryUsage)")+ // Inspect specific intervals and metric groups+ for entry in report.intervalEntries {+ let memoryMetrics = entry.metrics.filter { $0.group == .memory }+ for metric in memoryMetrics {+ switch metric {+ case .peakMemory(let measurement):+ print("[MetricKit] Peak memory: \(measurement)")+ default:+ break+ }+ }}}}โ // Called immediately when a diagnostic event occursโ func didReceive(_ payloads: [MXDiagnosticPayload]) {โ for payload in payloads {โ for crash in payload.crashDiagnostics ?? [] {โ print("[MetricKit] Crash signal: \(crash.signal ?? -1)")โ print("[MetricKit] Backtrace: \(crash.callStackTree)")+ private func collectDiagnostics() async {+ for await report in manager.diagnosticReports {+ // DiagnosticReports are also Codable+ switch report {+ case .crash(let crash):+ print("[MetricKit] Crash reason: \(crash.reason)")+ print("[MetricKit] Termination category: \(crash.terminationCategory)")+ for frame in crash.backtrace.frames {+ print(" \(frame)")+ }+ case .hang(let hang):+ print("[MetricKit] Hang duration: \(hang.duration)")+ case .memoryException(let mem):+ print("[MetricKit] Memory exception โ limit: \(mem.memoryLimit)")+ default:+ break}โ for hang in payload.hangDiagnostics ?? [] {โ print("[MetricKit] Hang duration: \(hang.hangDuration)")โ }}}}โ// MARK: - AppDelegate wiringโ@UIApplicationMainโclass AppDelegate: UIResponder, UIApplicationDelegate {โ func application(_ application: UIApplication,โ didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {โ LegacyPerformanceMonitor.shared.start()โ return true+// MARK: - App entry point+@main+struct MetricKitDemoApp: App {+ var body: some Scene {+ WindowGroup {+ ContentView()+ .task {+ // Start at launch on a detached task to avoid blocking the main actor+ await Task.detached(priority: .background) {+ await PerformanceMonitor.shared.startMonitoring()+ }.value+ }+ }}+}++struct ContentView: View {+ var body: some View {+ Text("Monitoring performance with MetricKit")+ .padding()+ }}
MetricManager must be instantiated and awaited at app launch โ delayed subscription risks missing reports. Keep the MetricManager instance alive for the lifetime of the app. DiagnosticReports are delivered immediately after the event, while MetricReports are daily. The StateReporting framework is new and separate from the core MetricKit import.
Reports are delivered on real devices; simulators do not generate metric reports. Daily metric reports require sustained device usage to populate.
More iOS 27 APIs land every week.
Get notified when new capabilities are published โ no noise, just signal.