Skip to main content

Memory Telemetry for iOS 27

swift

Observe MetricKit memory usage and termination reports with the iOS 27 async sequence API.

Use MetricManager to receive MetricKit reports as an async sequence. Keep the telemetry actor alive for as long as you want to collect reports, and send each sample to your existing analytics pipeline through the two closures.

import Foundation
import MetricKit

@available(iOS 27.0, *)
actor MemoryTelemetry {
    typealias MemoryUpload = @Sendable (
        _ name: String,
        _ bytes: Double
    ) -> Void

    typealias CountUpload = @Sendable (
        _ name: String,
        _ count: Int
    ) -> Void

    private let manager = MetricManager()
    private let uploadMemory: MemoryUpload
    private let uploadCount: CountUpload

    init(
        uploadMemory: @escaping MemoryUpload,
        uploadCount: @escaping CountUpload
    ) {
        self.uploadMemory = uploadMemory
        self.uploadCount = uploadCount
    }

    func observe() async {
        for await report in manager.metricReports {
            guard !Task.isCancelled else { return }

            for result in report.intervalEntries.fullDayEntry.values {
                switch result {
                case let .peakMemory(metric):
                    uploadMemory(
                        "peak_memory",
                        metric.value.converted(to: .bytes).value
                    )

                case let .suspendedMemory(metric):
                    uploadMemory(
                        "suspended_memory",
                        metric.value.average.converted(to: .bytes).value
                    )

                case let .foregroundTermination(metric):
                    uploadCount(
                        "fg_memory_limit",
                        metric.memoryLimitTerminationCount
                    )
                    uploadCount(
                        "fg_watchdog",
                        metric.watchdogTerminationCount
                    )

                case let .backgroundTermination(metric):
                    uploadCount(
                        "bg_memory_limit",
                        metric.memoryLimitTerminationCount
                    )
                    uploadCount(
                        "bg_system_pressure",
                        metric.systemPressureTerminationCount
                    )

                default:
                    break
                }
            }
        }
    }
}

Start observation from a long-lived owner, such as your app delegate or telemetry service:

if #available(iOS 27.0, *) {
    let telemetry = MemoryTelemetry(
        uploadMemory: { name, bytes in
            analytics.upload(name, value: bytes)
        },
        uploadCount: { name, count in
            analytics.upload(name, value: count)
        }
    )

    let observationTask = Task {
        await telemetry.observe()
    }
}

The observation task keeps telemetry alive while it runs. Store the task handle on your long-lived owner if you need to cancel observation. The plain default is intentional because this snippet handles only four cases from the much larger MetricResult enum.