CtrlK
BlogDocsLog inGet started
Tessl Logo

dpearson2699/swift-ios-skills

Agent skills for iOS, iPadOS, Swift, SwiftUI, and modern Apple framework development.

90

Quality

90%

Does it follow best practices?

Impact

Average score across 248 eval scenarios

SecuritybySnyk

Advisory

Suggest reviewing before use

Overview
Quality
Evals
Security
Files

metrickit-patterns.mdskills/metrickit/references/

MetricKit Extended Patterns

Overflow reference for the metrickit skill. Contains deeper payload analysis, export patterns, custom signpost metrics, and extended launch measurement.

Contents

  • Call Stack Trees
  • Custom Signpost Metrics
  • Exporting and Uploading Payloads
  • Past Payloads
  • Extended Launch Measurement
  • Xcode Organizer Integration

Call Stack Trees

MXCallStackTree is attached to each diagnostic (crash, hang, CPU exception, disk write, app launch). Use jsonRepresentation() to extract and symbolicate.

func handleCrash(_ crash: MXCrashDiagnostic) {
    let tree = crash.callStackTree
    let treeJSON = tree.jsonRepresentation()

    let exceptionType = crash.exceptionType
    let signal = crash.signal
    let reason = crash.terminationReason

    uploadDiagnostic(
        type: "crash",
        exceptionType: exceptionType,
        signal: signal,
        reason: reason,
        callStack: treeJSON
    )
}

func handleHang(_ hang: MXHangDiagnostic) {
    let tree = hang.callStackTree
    let duration = hang.hangDuration  // Measurement<UnitDuration>
    uploadDiagnostic(type: "hang", duration: duration, callStack: tree.jsonRepresentation())
}

The JSON structure contains an array of call stack frames with binary name, offset, and address. Symbolicate using atos or upload dSYMs to your analytics service.

Availability: MXCallStackTree — iOS 14.0+, iPadOS 14.0+, Mac Catalyst 14.0+, macOS 12.0+, visionOS 1.0+

Custom Signpost Metrics

Use mxSignpost with a MetricKit log handle to capture custom performance intervals. These appear in the daily MXMetricPayload under signpostMetrics.

Creating a Log Handle

let metricLog = MXMetricManager.makeLogHandle(category: "Networking")

Emitting Signposts

import os

func fetchData() async throws -> Data {
    mxSignpost(.begin, log: metricLog, name: "DataFetch")
    let data = try await URLSession.shared.data(from: url).0
    mxSignpost(.end, log: metricLog, name: "DataFetch")

    return data
}

For MetricKit custom metrics, create the log with MXMetricManager.makeLogHandle(category:) and leave the mxSignpost overload's advanced dso, signpostID, and format parameters at their documented defaults.

Reading Custom Metrics from Payload

if let signposts = payload.signpostMetrics {
    for metric in signposts {
        let name = metric.signpostName       // "DataFetch"
        let category = metric.signpostCategory // "Networking"
        let count = metric.totalCount
        if let intervalData = metric.signpostIntervalData {
            let avgMemory = intervalData.averageMemory
            let cumulativeCPUTime = intervalData.cumulativeCPUTime
        }
    }
}

The system limits the number of custom signpost metrics per log to reduce on-device overhead. Reserve custom metrics for critical code paths.

Exporting and Uploading Payloads

Both payload types conform to NSSecureCoding and provide jsonRepresentation() for easy serialization.

func persistPayload(_ jsonData: Data, from: Date? = nil, to: Date? = nil) {
    let fileName = "metrics_\(ISO8601DateFormatter().string(from: Date())).json"
    let url = FileManager.default.temporaryDirectory.appending(path: fileName)
    try? jsonData.write(to: url)
}

func uploadPayloads(_ jsonData: Data) {
    Task.detached(priority: .utility) {
        var request = URLRequest(url: URL(string: "https://api.example.com/metrics")!)
        request.httpMethod = "POST"
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        request.httpBody = jsonData
        _ = try? await URLSession.shared.data(for: request)
    }
}

Past Payloads

If the subscriber was not registered when payloads arrived, retrieve them using pastPayloads and pastDiagnosticPayloads. These return reports generated since the last allocation of the shared manager instance.

let pastMetrics = MXMetricManager.shared.pastPayloads
let pastDiags = MXMetricManager.shared.pastDiagnosticPayloads

Extended Launch Measurement

Track post-first-draw setup work (loading databases, restoring state) as part of the launch metric using extended launch measurement on iOS 16+, iPadOS 16+, Mac Catalyst 16+, macOS 13+, and visionOS 1+.

let taskID = MXLaunchTaskID("com.example.app.loadDatabase")

try MXMetricManager.extendLaunchMeasurement(forTaskID: taskID)
defer { try? MXMetricManager.finishExtendedLaunchMeasurement(forTaskID: taskID) }

restoreCachedState()
connectInitialSceneData()

Extended launch times appear under histogrammedExtendedLaunch in MXAppLaunchMetric.

Use these throwing type methods on the main thread. Start the first task before or during state restoration, or before the first scene becomes active. The system supports up to 16 tasks; task windows need to overlap, and extended launch measurement ends when all running tasks finish.

Xcode Organizer Integration

Xcode Organizer shows the same MetricKit data aggregated across all users who have opted in to share diagnostics. Use Organizer for trend analysis:

  • Metrics tab: Battery, performance, and disk-write metrics over time
  • Regressions tab: Automatic detection of metric regressions per version
  • Crashes tab: Crash logs with symbolicated stack traces

MetricKit on-device collection complements Organizer by letting you route raw data to your own backend for custom dashboards, alerting, and filtering by user cohort.

Apple Documentation Links

skills

README.md

tile.json