Meet the new MetricKit
Use iOS 27's Swift-first MetricKit APIs to collect metrics, diagnostics, and StateReporting context for faster performance triage.
TL;DR
- iOS 27 introduces a rebuilt, Swift-first MetricKit API centered on MetricManager, with new capabilities exclusive to the new API surface.
- MetricKit provides daily metric reports for app health signals such as launch time, hangs, CPU, memory, GPU, disk writes, network transfers, display metrics, and the new Metal frame rate metric.
- Diagnostic reports are delivered immediately for failures such as crashes and hangs; iOS 27 adds memory exception diagnostics and a crash termination category for correlating diagnostics with metrics.
- StateReporting lets apps tag meaningful app states and metadata so MetricKit can break metrics down by user flow, configuration, or experiment instead of reporting only blended app-wide averages.
What changed in MetricKit
MetricKit is the collection layer in a performance workflow: collect data, analyze trends, triage problems, fix them, and monitor the results. It provides two complementary data types: metrics for overall performance trends and diagnostics for identifying code paths behind specific failures.
In iOS 27, MetricKit has been rebuilt with a modern Swift-first API. The new MetricManager-based APIs are the future of the framework, and the new capabilities discussed in the session are exclusive to them. Apps still using MXMetricManager should migrate to MetricManager to access these features.
- Metrics cover app health signals such as launch time, hangs, animation/display behavior, CPU, GPU, memory, disk writes, network transfers, and Metal frame rate.
- Diagnostics cover events such as crashes, hangs, and memory exceptions, with structured data useful for triage.
- MetricKit can now intersect metrics with app state reported through StateReporting.
Collect and analyze metric reports
MetricKit continuously collects metrics as people use an app and delivers them in daily reports. Each report includes a full-day entry and may include smaller interval entries, typically spanning a few hours, when metrics are available for those windows.
Metrics are organized into groups such as .cpu, .memory, .display, and .gpu. Reports are Codable, so a common production workflow is to encode the complete report and send it to a server for aggregation across devices. Server-side analysis should establish baselines and monitor statistically meaningful changes over time.
- Set up MetricManager at app startup to avoid losing data from delayed subscription.
- Keep the MetricManager alive so its async streams continue delivering reports.
- Use intervalEntries for full-day and smaller-window metric data.
- Filter by metricGroup and switch on metric cases when extracting specific values such as peak memory.
Receive and encode metric reports
Subscribes to MetricKit metric reports and encodes each Codable report for server-side ingestion.
import MetricKit
let manager = MetricManager()
for await report in manager.metricReports {
let jsonData = try JSONEncoder().encode(report)
sendToServer(jsonData)
}Inspect memory metrics
Shows the report structure: iterate interval entries, filter by metric group, then switch on individual metric cases.
import MetricKit
for await report in manager.metricReports {
for entry in report.intervalEntries {
let memoryMetrics = entry.values.filter { $0.metricGroup == .memory }
for metric in memoryMetrics {
switch metric {
case .peakMemory(let peak):
processPeakMemory(peak)
default:
break
}
}
}
}Use diagnostics for triage
Diagnostics are captured on device when something goes wrong and are delivered immediately through MetricKit. They help move from a trend, such as rising crashes or hangs, to a concrete code path that can be investigated.
Crash diagnostics include a symbolicated backtrace plus metadata such as exception type and termination reason. In iOS 27, crash diagnostics also include a termination category indicating how the crash was counted in metrics, making it easier to correlate abnormal termination trends with individual diagnostics. iOS 27 also adds memory exception diagnostics for app or extension termination due to exceeding memory limits.
- Listen to diagnosticReports as soon as the app launches, ideally from a detached task or dedicated service.
- Encode DiagnosticReport values with JSONEncoder when forwarding to an analytics backend.
- Switch on report.result to handle crashes, hangs, and other diagnostics differently.
Receive and encode diagnostic reports
Receives immediate diagnostic reports and forwards the structured Codable payload to a server.
import MetricKit
let manager = MetricManager()
for await report in manager.diagnosticReports {
let jsonData = try JSONEncoder().encode(report)
sendToServer(jsonData)
}Extract crash and hang details
Accesses structured diagnostic details, including crash backtrace, termination reason, and termination category.
import MetricKit
for await report in manager.diagnosticReports {
switch report.result {
case .crash(let crash):
let backtrace = crash.callStackTree
let reason = crash.terminationReason
let category = crash.terminationCategory
processCrash(backtrace: backtrace, reason: reason, category: category)
case .hang(let hang):
processHangDiagnostic(hang)
default:
break
}
}Add app context with StateReporting
App-wide metrics can hide where a problem actually occurs. StateReporting lets an app report meaningful states, such as the active tab, user flow, app configuration, or experiment variant, so MetricKit can aggregate metrics separately for each state.
States are scoped to domains. A domain represents a function or area of the app and can have only one active state at a time. Separate domains allow multiple dimensions to be active concurrently, such as active tab and database batch-size experiment. State reporting uses a transition model: the app reports the state it is moving to, and MetricKit tracks how long it remains there.
- Create a StateReportingDomain, usually named with a reverse-DNS string.
- Register enabled state reporting domains when constructing MetricManager.
- Report stable, meaningful transitions rather than transient UI events.
- Use @ReportableMetadata for structured state metadata such as list size or sort order.
- MetricReport.stateEntries contains state-aware metrics; it is empty when no states are reported.
Report app state transitions
Enables a StateReporting domain for MetricKit and reports a transition into a named app state.
import MetricKit
import StateReporting
let domain = StateReportingDomain("com.metrickitsample.tabs")
let manager = MetricManager(enabledStateReportingDomains: [domain])
let reporter = StateReporter.reporter(for: domain.rawValue)
reporter.reportTransition(to: "Reports")Attach structured metadata to states
Adds stable structured metadata to a reported state so performance data can be segmented by app-defined configuration.
import StateReporting
@ReportableMetadata
struct ViewConfiguration {
let listSize: String
let isSorted: Bool
}
let reporter = StateReporter.reporter(
for: domain.rawValue,
stableMetadata: ViewConfiguration.self
)
reporter.reportTransition(
to: "Reports",
stableMetadata: ViewConfiguration(listSize: "large", isSorted: false)
)Encode and interpret state-aware reports
After states are reported, MetricKit surfaces StateEntry values alongside interval entries. Each StateEntry contains metrics aggregated across time spent in an individual state, enabling comparisons such as scroll hitch rate in one tab versus another.
When sending reports to a server, the encoded report can be grouped by state reporting domain. This helps analytics pipelines process interval entries and state entries by domain and state.
- Use narrowly scoped domains so each app area has interpretable state data.
- Avoid creating too many states; overly granular state models can make the data harder to interpret and there are upper limits to reduce overhead.
- Plan states so a regression points to a fixable area or configuration.
- Validate reported states with the Points of Interest instrument before shipping.
Encode reports grouped by StateReporting domain
Configures JSONEncoder so encoded MetricReport data is grouped by each StateReporting domain and state.
import MetricKit
for await report in manager.metricReports {
let encoder = JSONEncoder()
let formatKey = MetricReport.encodingFormatKey
encoder.userInfo[formatKey] = MetricReport.EncodingFormat.byStateReportingDomain
let jsonData = try encoder.encode(report)
sendToServer(jsonData)
}Resources
- Getting started with StateReporting
- Analyzing app performance with MetricKit
- Monitoring app performance with MetricKit
- Track performance by app state using MetricKit
- MetricKit
Related Sessions
- Find and fix performance issues in your Metal games
- Profile, fix, and verify: Improve app responsiveness with Instruments
Rev up your CarPlay app
Learn iOS 27 CarPlay updates for video browsing, richer media UI, voice control overlays, navigation panels, route sharing, and simulator testing.
Expand the capabilities of your Virtualization app
Use macOS 27 Virtualization APIs for guest provisioning, USB passthrough, custom vmnet networks, DiskImageKit images, and custom Virtio devices.