iOS 27 rebuilds MetricKit from the ground up with a modern, Swift-first API featuring async/await streams for metric and diagnostic reports, plus new contextual state reporting via the StateReporting framework. The redesign adds new diagnostics (memory exceptions), new metrics (Metal frame rate), and structured metric delivery intersected with app-defined states.
⢠Entire API rebuilt with async/await streams replacing MXMetricManagerSubscriber delegate callbacks
⢠New StateReporting framework allows metrics to be intersected with developer-defined app states (e.g. active tab, feature flags)
⢠New Metal frame rate metric added for game developers
⢠New memory exception diagnostics report when app/extension is terminated for exceeding memory limits
⢠Crash diagnostics now include a termination category correlating crashes with metric trends
⢠Async/await streams replace delegate callbacks, making it trivial to collect and forward daily metric and diagnostic reports to your analytics server with minimal boilerplate
⢠New StateReporting integration lets you slice metrics (hangs, hitches, CPU) by your own app states ā e.g. per-tab or per-feature ā so you pinpoint exactly where performance degrades rather than seeing blended averages
⢠New Metal frame rate metric and memory exception diagnostics give game developers and all apps richer signals to act on before users churn
Shows how to start receiving daily metric reports and immediate diagnostic reports using the new async/await MetricKit API, then log hang and crash diagnostics with their backtraces.
import MetricKit
import SwiftUI
// MARK: - Performance Monitor (start at app launch, keep alive)
actor PerformanceMonitor {
static let shared = PerformanceMonitor()
private let manager = MetricManager()
func startMonitoring() {
// Stream daily metric reports
Task.detached(priority: .utility) {
for await report in await self.manager.metricReports {
await self.handleMetricReport(report)
}
}
// Stream diagnostic reports (delivered immediately after events)
Task.detached(priority: .utility) {
for await report in await self.manager.diagnosticReports {
await self.handleDiagnosticReport(report)
}
}
}
private func handleMetricReport(_ report: MetricReport) {
// Encode and ship to analytics server
let encoder = JSONEncoder()
encoder.dateEncodingStrategy = .iso8601
if let data = try? encoder.encode(report),
let json = String(data: data, encoding: .utf8) {
print("[MetricKit] Sending metric report (\(json.count) bytes) to server")
}
// Inspect memory metrics across all interval entries
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
}
}
}
}
private func handleDiagnosticReport(_ report: DiagnosticReport) {
let encoder = JSONEncoder()
if let data = try? encoder.encode(report) {
print("[MetricKit] Diagnostic report: \(data.count) bytes")
}
switch report {
case .crash(let crash):
let frames = crash.backtrace.frames
.map { $0.description }
.joined(separator: "\n ")
print("""
[MetricKit] Crash detected
Reason: \(crash.terminationReason)
Category: \(crash.terminationCategory)
Backtrace:\n \(frames)
""")
case .hang(let hang):
let frames = hang.backtrace.frames
.map { $0.description }
.joined(separator: "\n ")
print("""
[MetricKit] Hang detected (\(hang.duration))
Backtrace:\n \(frames)
""")
case .memoryException(let mem):
print("[MetricKit] Memory exception ā limit: \(mem.memoryLimit), usage at termination: \(mem.memoryUsage)")
default:
print("[MetricKit] Other diagnostic received")
}
}
}
// MARK: - App Entry Point
@main
struct MetricKitDemoApp: App {
init() {
Task { await PerformanceMonitor.shared.startMonitoring() }
}
var body: some Scene {
WindowGroup { ContentView() }
}
}
struct ContentView: View {
var body: some View {
Text("MetricKit monitoring active")
.padding()
}
}MetricManager must be created at app startup and kept alive for the lifetime of the process ā late subscription risks missing reports. The new Swift-first APIs are separate from and do not replace the legacy MXMetricManager delegate approach; all new capabilities are exclusive to the new API surface. StateReporting domains can only have one active state at a time per domain.
Reports are delivered on-device daily; diagnostic reports arrive immediately after an event. No Apple Intelligence hardware required.
More iOS 27 APIs land every week.
Get notified when new capabilities are published ā no noise, just signal.