Agent skills for iOS, iPadOS, Swift, SwiftUI, and modern Apple framework development.
80
100%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Advisory
Suggest reviewing before use
Use these patterns after choosing the iOS/iPadOS 27 MetricManager path or the isolated iOS 26 MXMetricManager compatibility path in the main skill.
Beta-sensitive: The iOS/iPadOS 27 examples follow Apple's current beta documentation. They have not been locally compiler-verified because Xcode 27 is unavailable. Re-check availability and signatures with the shipping SDK.
The report consumer should perform one small, reliable operation: encode and durably enqueue the complete report.
Use an outbox record that includes:
MetricReport or DiagnosticReport bytesKeep raw reports until the server accepts them or a documented retention limit expires. Parse derived fields in a separate worker so a parser failure cannot discard the source evidence.
@available(iOS 27.0, *)
func enqueue(_ report: MetricReport) async throws {
let data = try JSONEncoder().encode(report)
try await outbox.insert(
kind: .metric,
payload: data,
deduplicationKey: stableKey(for: report)
)
}
@available(iOS 27.0, *)
func enqueue(_ report: DiagnosticReport) async throws {
let data = try JSONEncoder().encode(report)
try await outbox.insert(
kind: .diagnostic,
payload: data,
deduplicationKey: stableKey(for: report)
)
}outbox and stableKey(for:) are application-owned abstractions. Build the key from stable report metadata and the encoded report rather than memory identity.
The upload worker should:
Do not rely on a modern backfill call. Apple's iOS 27 MetricManager documentation exposes the live metricReports and diagnosticReports sequences, not replacements for pastPayloads or pastDiagnosticPayloads.
Use MetricReport.timeRange to establish the aggregation window. Treat environment as optional and retain the entire interval/state structure even if the first dashboard only uses intervalEntries.fullDayEntry.
@available(iOS 27.0, *)
func analyze(_ report: MetricReport) {
let fullDay = report.intervalEntries.fullDayEntry
for result in fullDay.values {
switch result {
case .hangTime(let value):
recordHangTime(value, interval: report.timeRange)
case .scrollHitchTime(let value):
recordScrollHitchTime(value, interval: report.timeRange)
case .cpuTime(let value):
recordCPUTime(value, interval: report.timeRange)
case .peakMemory(let value):
recordPeakMemory(value, interval: report.timeRange)
case .foregroundTermination(let value):
recordForegroundTerminations(value, interval: report.timeRange)
case .extendedLaunch(let value):
recordExtendedLaunch(value, interval: report.timeRange)
case .signpostInterval(let value):
recordSignpostInterval(value, interval: report.timeRange)
@unknown default:
break // The raw encoded report remains in the outbox.
}
}
}Metric reports are normally daily aggregates. Compare distributions across app versions, hardware, OS versions, and relevant environment fields. Avoid attributing a full-day value to one action without supporting local traces.
Process the report only after durable storage:
@available(iOS 27.0, *)
func analyze(_ report: DiagnosticReport) {
switch report.result {
case .crash(let crash):
symbolicate(crash.callStackTree)
case .hang(let hang):
recordHang(hang.hangDuration)
symbolicate(hang.callStackTree)
case .cpuException(let exception):
recordCPUException(
totalCPUTime: exception.totalCPUTime,
sampledTime: exception.totalSampledTime
)
symbolicate(exception.callStackTree)
case .diskWriteException(let exception):
recordDiskWriteException(exception.totalBytesWritten)
symbolicate(exception.callStackTree)
case .appLaunch(let launch):
recordLaunchDiagnostic(launch.launchDuration)
symbolicate(launch.callStackTree)
case .memoryException(let memory):
symbolicate(memory.callStackTree)
@unknown default:
break // Preserve and revisit the raw report after updating the parser.
}
}Diagnostics are event-based and intended for prompt delivery when the system produces them. They are not a complete ledger of all crashes, hangs, launches, or resource events.
Load this catalog when mapping exact MetricResult cases into a parser or dashboard:
| Area | MetricResult cases |
|---|---|
| Responsiveness | hangTime, hitchTime, scrollHitchTime |
| Terminations | foregroundTermination, backgroundTermination |
| Runtime | totalForegroundTime, totalBackgroundTime, totalBackgroundAudioTime, totalBackgroundLocationTime, locationActivityTime |
| CPU and memory | cpuTime, cpuInstructionsCount, peakMemory, suspendedMemory |
| Network | totalWiFiUpload, totalWiFiDownload, totalCellularUpload, totalCellularDownload, cellularConditionTime |
| Launch | timeToFirstDraw, applicationResumeTime, optimizedTimeToFirstDraw, extendedLaunch |
| Storage | logicalDiskWrites, totalFileCount, totalFileSize, totalDiskSpaceCapacity |
| Display and GPU | pixelLuminance, gpuTime, metalFrameRate |
| Custom intervals | signpostInterval |
CallStackTree is Codable and retains the structure needed for later analysis. Keep the whole tree plus binaryInfo before producing flattened display frames.
Use forEachFrame when the consumer only needs frame traversal. Use callStackThreads when thread relationships matter. Symbolicate against the exact archive and dSYMs for the report's app build.
Recommended server-side pipeline:
binaryInfo.Use the retained iOS 27 manager to create a log handle:
let log = manager.logHandle(category: "Database")
mxSignpost(.begin, log: log, name: "Migration")
await migrateDatabase()
mxSignpost(.end, log: log, name: "Migration")The resulting aggregate is a MetricResult.signpostInterval value. Keep signpost category and name stable across releases so comparisons remain meaningful.
Prefer mxSignpost for MetricKit intervals that need resource measurements. Apple's documentation notes that OSSignposter intervals created with the MetricKit handle do not populate the same resource-measurement fields.
Track launch-critical work through the retained manager:
@MainActor
@available(iOS 27.0, *)
func loadLaunchData(using manager: MetricManager) async throws -> AppData {
try await manager.trackLaunchTask(
id: "load-app-data",
onTrackingError: { error in
recordLaunchTrackingError(error)
}
) {
try await loadAppData()
}
}The tracked closure's result and application error propagate normally. onTrackingError reports MetricManager.LaunchTaskError without replacing the closure's outcome. Keep LaunchTaskID values stable and do not track noncritical background work as launch work.
Extended launch results appear under MetricResult.extendedLaunch.
Use this branch only for deployment targets that include iOS/iPadOS 26 or earlier supported versions. MXMetricManager is deprecated in iOS 27.
Register one retained subscriber as early as possible:
final class LegacyMetricsSubscriber: NSObject, MXMetricManagerSubscriber {
static let shared = LegacyMetricsSubscriber()
private override init() {
super.init()
}
func start() {
let manager = MXMetricManager.shared
manager.add(self)
// Legacy-only backfill APIs.
persist(manager.pastPayloads)
persist(manager.pastDiagnosticPayloads)
}
func didReceive(_ payloads: [MXMetricPayload]) {
persist(payloads)
}
func didReceive(_ payloads: [MXDiagnosticPayload]) {
persist(payloads)
}
private func persist(_ payloads: [MXMetricPayload]) {
for payload in payloads {
durableLegacyOutbox.enqueue(payload.jsonRepresentation())
}
}
private func persist(_ payloads: [MXDiagnosticPayload]) {
for payload in payloads {
durableLegacyOutbox.enqueue(payload.jsonRepresentation())
}
}
}The outbox calls above are application-owned placeholders. Make them durable before parsing or uploading. Prevent duplicate add(_:) calls in the app's lifecycle code.
Route at launch:
if #available(iOS 27.0, *) {
modernMetricsService.start()
} else {
LegacyMetricsSubscriber.shared.start()
}let log = MXMetricManager.makeLogHandle(category: "Database")
mxSignpost(.begin, log: log, name: "Migration")
await migrateDatabase()
mxSignpost(.end, log: log, name: "Migration")Legacy signpost aggregates are available through MXMetricPayload.signpostMetrics.
try MXMetricManager.extendLaunchMeasurement(forTaskID: "load-app-data")
defer {
try? MXMetricManager.finishExtendedLaunchMeasurement(forTaskID: "load-app-data")
}
await loadAppData()Pair the calls on every control-flow path. Use defer where it can guarantee completion without changing asynchronous behavior.
Start with Organizer when Apple-provided aggregation is sufficient. A custom ingestion backend adds value when the product needs:
Keep privacy and data-minimization requirements explicit. Avoid attaching user content or direct identifiers to MetricKit reports.
.tessl-plugin
skills
accessorysetupkit
references
activitykit
references
adattributionkit
references
alarmkit
references
app-clips
app-intents
app-store-optimization
app-store-review
apple-on-device-ai
appmigrationkit
references
audioaccessorykit
references
authentication
references
avkit
references
background-processing
references
browserenginekit
references
callkit
references
carplay
references
cloudkit
references
contacts-framework
references
core-bluetooth
references
core-data
core-motion
references
core-nfc
references
coreml
references
cryptokit
references
cryptotokenkit
references
debugging-instruments
device-integrity
references
dockkit
references
energykit
references
eventkit
references
financekit
references
focus-engine
gamekit
references
healthkit
references
homekit
references
ios-accessibility
ios-ettrace-performance
ios-localization
ios-memgraph-analysis
ios-networking
ios-simulator
references
mapkit
metrickit
references
musickit
references
natural-language
references
paperkit
references
passkit
references
pdfkit
references
pencilkit
references
permissionkit
references
photokit
push-notifications
realitykit
references
relevancekit
references
scenekit
references
sensorkit
references
speech-recognition
references
spritekit
references
storekit
swift-api-design-guidelines
swift-architecture
references
swift-charts
references
swift-codable
references
swift-concurrency
swift-formatstyle
references
swift-language
swift-security
references
swift-testing
swiftdata
swiftlint
swiftui-animation
swiftui-gestures
references
swiftui-layout-components
swiftui-liquid-glass
references
swiftui-patterns
swiftui-performance
swiftui-uikit-interop
swiftui-webkit
tabletopkit
references
tipkit
references
vision-framework
weatherkit
references
widgetkit
references