@mmerterden/multi-agent-pipeline 20.7.0 → 20.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/LICENSE +0 -10
- package/docs/facts.json +1 -1
- package/manifest.json +285 -285
- package/package.json +3 -3
- package/pipeline/lib/redact.mjs +3 -2
- package/pipeline/scripts/_notices.mjs +1 -1
- package/pipeline/scripts/gen-skills-index.mjs +13 -1
- package/pipeline/scripts/pre-commit-check.sh +4 -0
- package/pipeline/skills/.skill-manifest.json +69 -69
- package/pipeline/skills/shared/README.md +70 -70
- package/pipeline/skills/shared/external/alarmkit/SKILL.md +373 -381
- package/pipeline/skills/shared/external/alarmkit/evals/evals.json +23 -18
- package/pipeline/skills/shared/external/alarmkit/references/alarmkit-patterns.md +328 -378
- package/pipeline/skills/shared/external/app-clips/SKILL.md +260 -160
- package/pipeline/skills/shared/external/app-clips/evals/evals.json +27 -27
- package/pipeline/skills/shared/external/app-clips/references/data-handoff-notifications-location.md +150 -83
- package/pipeline/skills/shared/external/app-clips/references/routing-and-experiences.md +135 -83
- package/pipeline/skills/shared/external/app-clips/references/size-capabilities-and-promotion.md +143 -85
- package/pipeline/skills/shared/external/app-intents/SKILL.md +302 -304
- package/pipeline/skills/shared/external/app-intents/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/app-intents/references/appintents-advanced.md +594 -894
- package/pipeline/skills/shared/external/app-store-optimization/SKILL.md +345 -277
- package/pipeline/skills/shared/external/app-store-optimization/evals/evals.json +27 -23
- package/pipeline/skills/shared/external/app-store-optimization/references/keyword-research-methodology.md +107 -121
- package/pipeline/skills/shared/external/app-store-optimization/references/product-page-variants.md +145 -165
- package/pipeline/skills/shared/external/app-store-review/SKILL.md +306 -326
- package/pipeline/skills/shared/external/app-store-review/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/app-store-review/references/privacy-manifest.md +105 -67
- package/pipeline/skills/shared/external/app-store-review/references/review-checklists.md +114 -101
- package/pipeline/skills/shared/external/apple-on-device-ai/SKILL.md +335 -360
- package/pipeline/skills/shared/external/apple-on-device-ai/evals/evals.json +24 -27
- package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-conversion.md +215 -322
- package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-optimization.md +161 -256
- package/pipeline/skills/shared/external/apple-on-device-ai/references/foundation-models.md +277 -387
- package/pipeline/skills/shared/external/apple-on-device-ai/references/mlx-swift.md +196 -210
- package/pipeline/skills/shared/external/authentication/SKILL.md +277 -381
- package/pipeline/skills/shared/external/authentication/evals/evals.json +25 -25
- package/pipeline/skills/shared/external/authentication/references/keychain-biometric.md +135 -178
- package/pipeline/skills/shared/external/authentication/references/passkeys.md +111 -147
- package/pipeline/skills/shared/external/avkit/SKILL.md +267 -364
- package/pipeline/skills/shared/external/avkit/evals/evals.json +26 -26
- package/pipeline/skills/shared/external/avkit/references/avkit-patterns.md +375 -493
- package/pipeline/skills/shared/external/background-processing/SKILL.md +274 -384
- package/pipeline/skills/shared/external/background-processing/evals/evals.json +22 -22
- package/pipeline/skills/shared/external/background-processing/references/background-task-patterns.md +173 -321
- package/pipeline/skills/shared/external/callkit-voip/SKILL.md +290 -371
- package/pipeline/skills/shared/external/callkit-voip/evals/evals.json +24 -24
- package/pipeline/skills/shared/external/callkit-voip/references/callkit-patterns.md +175 -343
- package/pipeline/skills/shared/external/cloudkit-sync/SKILL.md +292 -381
- package/pipeline/skills/shared/external/cloudkit-sync/evals/evals.json +33 -30
- package/pipeline/skills/shared/external/cloudkit-sync/references/cloudkit-patterns.md +227 -355
- package/pipeline/skills/shared/external/contacts-framework/SKILL.md +197 -346
- package/pipeline/skills/shared/external/contacts-framework/evals/evals.json +19 -21
- package/pipeline/skills/shared/external/contacts-framework/references/contacts-patterns.md +169 -308
- package/pipeline/skills/shared/external/core-bluetooth/SKILL.md +228 -376
- package/pipeline/skills/shared/external/core-bluetooth/evals/evals.json +25 -22
- package/pipeline/skills/shared/external/core-bluetooth/references/ble-patterns.md +257 -337
- package/pipeline/skills/shared/external/core-data/SKILL.md +302 -368
- package/pipeline/skills/shared/external/core-data/evals/evals.json +30 -27
- package/pipeline/skills/shared/external/core-motion/SKILL.md +235 -324
- package/pipeline/skills/shared/external/core-motion/evals/evals.json +31 -27
- package/pipeline/skills/shared/external/core-motion/references/motion-patterns.md +210 -310
- package/pipeline/skills/shared/external/core-nfc/SKILL.md +292 -366
- package/pipeline/skills/shared/external/core-nfc/evals/evals.json +22 -24
- package/pipeline/skills/shared/external/core-nfc/references/nfc-patterns.md +233 -329
- package/pipeline/skills/shared/external/coreml/SKILL.md +323 -367
- package/pipeline/skills/shared/external/coreml/evals/evals.json +24 -21
- package/pipeline/skills/shared/external/coreml/references/coreml-swift-integration.md +562 -565
- package/pipeline/skills/shared/external/cryptokit/SKILL.md +253 -394
- package/pipeline/skills/shared/external/cryptokit/evals/evals.json +20 -18
- package/pipeline/skills/shared/external/cryptokit/references/cryptokit-patterns.md +299 -488
- package/pipeline/skills/shared/external/debugging-instruments/SKILL.md +270 -323
- package/pipeline/skills/shared/external/debugging-instruments/evals/evals.json +27 -30
- package/pipeline/skills/shared/external/debugging-instruments/references/instruments-guide.md +167 -315
- package/pipeline/skills/shared/external/debugging-instruments/references/lldb-patterns.md +140 -193
- package/pipeline/skills/shared/external/device-integrity/SKILL.md +236 -353
- package/pipeline/skills/shared/external/device-integrity/evals/evals.json +25 -21
- package/pipeline/skills/shared/external/device-integrity/references/device-integrity-patterns.md +159 -197
- package/pipeline/skills/shared/external/energykit/SKILL.md +225 -392
- package/pipeline/skills/shared/external/energykit/evals/evals.json +29 -28
- package/pipeline/skills/shared/external/energykit/references/energykit-patterns.md +174 -470
- package/pipeline/skills/shared/external/eventkit-calendar/SKILL.md +261 -383
- package/pipeline/skills/shared/external/eventkit-calendar/evals/evals.json +25 -22
- package/pipeline/skills/shared/external/eventkit-calendar/references/eventkit-patterns.md +165 -268
- package/pipeline/skills/shared/external/healthkit/SKILL.md +252 -303
- package/pipeline/skills/shared/external/healthkit/evals/evals.json +24 -23
- package/pipeline/skills/shared/external/healthkit/references/healthkit-patterns.md +369 -523
- package/pipeline/skills/shared/external/homekit-matter/SKILL.md +233 -348
- package/pipeline/skills/shared/external/homekit-matter/evals/evals.json +27 -22
- package/pipeline/skills/shared/external/homekit-matter/references/matter-commissioning.md +199 -305
- package/pipeline/skills/shared/external/ios-accessibility/SKILL.md +368 -340
- package/pipeline/skills/shared/external/ios-accessibility/evals/evals.json +28 -27
- package/pipeline/skills/shared/external/ios-accessibility/references/a11y-patterns.md +314 -260
- package/pipeline/skills/shared/external/ios-accessibility/references/media-accessibility.md +97 -67
- package/pipeline/skills/shared/external/ios-accessibility/references/nutrition-labels.md +165 -101
- package/pipeline/skills/shared/external/ios-localization/SKILL.md +258 -371
- package/pipeline/skills/shared/external/ios-localization/evals/evals.json +23 -23
- package/pipeline/skills/shared/external/ios-localization/references/formatstyle-locale.md +283 -491
- package/pipeline/skills/shared/external/ios-localization/references/string-catalogs.md +313 -440
- package/pipeline/skills/shared/external/ios-networking/SKILL.md +265 -341
- package/pipeline/skills/shared/external/ios-networking/evals/evals.json +24 -24
- package/pipeline/skills/shared/external/ios-networking/references/background-websocket.md +425 -652
- package/pipeline/skills/shared/external/ios-networking/references/file-storage-patterns.md +143 -285
- package/pipeline/skills/shared/external/ios-networking/references/lightweight-clients.md +93 -53
- package/pipeline/skills/shared/external/ios-networking/references/network-framework.md +231 -456
- package/pipeline/skills/shared/external/ios-networking/references/urlsession-patterns.md +517 -784
- package/pipeline/skills/shared/external/ios-simulator/SKILL.md +265 -393
- package/pipeline/skills/shared/external/ios-simulator/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/ios-simulator/references/simctl-commands.md +177 -270
- package/pipeline/skills/shared/external/live-activities/SKILL.md +318 -360
- package/pipeline/skills/shared/external/live-activities/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/live-activities/references/activitykit-patterns.md +478 -710
- package/pipeline/skills/shared/external/localization-reuse-map/SKILL.md +3 -3
- package/pipeline/skills/shared/external/localization-reuse-map/example-mapping.json +1 -1
- package/pipeline/skills/shared/external/localization-reuse-map/reference/format-and-output.md +8 -7
- package/pipeline/skills/shared/external/localization-reuse-map/reference/publish-and-snapshot.md +2 -2
- package/pipeline/skills/shared/external/localization-reuse-map/reference/sources-and-recipes.md +5 -2
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/_shared.py +100 -0
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-artifact.py +45 -26
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-spreadsheet.py +14 -16
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-annotations.py +12 -5
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-legacy-labels.py +2 -1
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/publish-confluence.py +6 -5
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/render-overlay.py +44 -18
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-legacy-values.py +5 -2
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-new-values.py +10 -11
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/scan-screen-keys.py +4 -33
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/verify-map.py +12 -59
- package/pipeline/skills/shared/external/mapkit-location/SKILL.md +297 -267
- package/pipeline/skills/shared/external/mapkit-location/evals/evals.json +28 -24
- package/pipeline/skills/shared/external/mapkit-location/references/mapkit-corelocation-patterns.md +378 -532
- package/pipeline/skills/shared/external/mapkit-location/references/mapkit-patterns.md +397 -499
- package/pipeline/skills/shared/external/metrickit-diagnostics/SKILL.md +165 -348
- package/pipeline/skills/shared/external/metrickit-diagnostics/evals/evals.json +26 -23
- package/pipeline/skills/shared/external/metrickit-diagnostics/references/metrickit-patterns.md +123 -130
- package/pipeline/skills/shared/external/musickit-audio/SKILL.md +189 -315
- package/pipeline/skills/shared/external/musickit-audio/evals/evals.json +22 -21
- package/pipeline/skills/shared/external/musickit-audio/references/musickit-patterns.md +181 -270
- package/pipeline/skills/shared/external/natural-language/SKILL.md +188 -340
- package/pipeline/skills/shared/external/natural-language/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/natural-language/references/translation-patterns.md +171 -225
- package/pipeline/skills/shared/external/passkit-wallet/SKILL.md +258 -392
- package/pipeline/skills/shared/external/passkit-wallet/evals/evals.json +30 -29
- package/pipeline/skills/shared/external/passkit-wallet/references/wallet-passes.md +164 -231
- package/pipeline/skills/shared/external/pdfkit/SKILL.md +312 -344
- package/pipeline/skills/shared/external/pdfkit/evals/evals.json +19 -19
- package/pipeline/skills/shared/external/pdfkit/references/pdfkit-patterns.md +413 -624
- package/pipeline/skills/shared/external/pencilkit-drawing/SKILL.md +242 -358
- package/pipeline/skills/shared/external/pencilkit-drawing/evals/evals.json +25 -21
- package/pipeline/skills/shared/external/pencilkit-drawing/references/pencilkit-patterns.md +161 -226
- package/pipeline/skills/shared/external/permissionkit/SKILL.md +282 -400
- package/pipeline/skills/shared/external/permissionkit/evals/evals.json +27 -30
- package/pipeline/skills/shared/external/permissionkit/references/permissionkit-patterns.md +237 -350
- package/pipeline/skills/shared/external/photos-camera-media/SKILL.md +276 -325
- package/pipeline/skills/shared/external/photos-camera-media/references/av-playback.md +299 -545
- package/pipeline/skills/shared/external/photos-camera-media/references/camera-capture.md +344 -588
- package/pipeline/skills/shared/external/photos-camera-media/references/image-loading-caching.md +316 -660
- package/pipeline/skills/shared/external/photos-camera-media/references/photokit-patterns.md +270 -416
- package/pipeline/skills/shared/external/push-notifications/SKILL.md +312 -340
- package/pipeline/skills/shared/external/push-notifications/evals/evals.json +27 -26
- package/pipeline/skills/shared/external/push-notifications/references/notification-patterns.md +328 -485
- package/pipeline/skills/shared/external/push-notifications/references/rich-notifications.md +327 -560
- package/pipeline/skills/shared/external/realitykit-ar/SKILL.md +218 -410
- package/pipeline/skills/shared/external/realitykit-ar/evals/evals.json +24 -27
- package/pipeline/skills/shared/external/realitykit-ar/references/realitykit-patterns.md +221 -348
- package/pipeline/skills/shared/external/shareplay-activities/SKILL.md +222 -393
- package/pipeline/skills/shared/external/shareplay-activities/evals/evals.json +23 -24
- package/pipeline/skills/shared/external/shareplay-activities/references/shareplay-patterns.md +280 -420
- package/pipeline/skills/shared/external/skill-creator/template.md +7 -1
- package/pipeline/skills/shared/external/speech-recognition/SKILL.md +217 -421
- package/pipeline/skills/shared/external/speech-recognition/evals/evals.json +23 -26
- package/pipeline/skills/shared/external/speech-recognition/references/speechanalyzer-patterns.md +133 -125
- package/pipeline/skills/shared/external/storekit/SKILL.md +228 -204
- package/pipeline/skills/shared/external/storekit/evals/evals.json +27 -24
- package/pipeline/skills/shared/external/storekit/references/app-review-guidelines.md +98 -109
- package/pipeline/skills/shared/external/storekit/references/core-patterns.md +302 -241
- package/pipeline/skills/shared/external/storekit/references/storekit-advanced.md +356 -649
- package/pipeline/skills/shared/external/swift-api-design-guidelines/SKILL.md +274 -399
- package/pipeline/skills/shared/external/swift-api-design-guidelines/evals/evals.json +22 -24
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/argument-labels-and-parameters.md +107 -108
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/conventions-and-special-rules.md +93 -165
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/naming-and-clarity.md +99 -137
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/side-effects-and-mutating-pairs.md +77 -120
- package/pipeline/skills/shared/external/swift-architecture/SKILL.md +334 -350
- package/pipeline/skills/shared/external/swift-architecture/evals/evals.json +22 -22
- package/pipeline/skills/shared/external/swift-charts/SKILL.md +208 -394
- package/pipeline/skills/shared/external/swift-charts/evals/evals.json +27 -30
- package/pipeline/skills/shared/external/swift-charts/references/charts-patterns.md +351 -762
- package/pipeline/skills/shared/external/swift-codable/SKILL.md +339 -343
- package/pipeline/skills/shared/external/swift-codable/evals/evals.json +20 -20
- package/pipeline/skills/shared/external/swift-concurrency/SKILL.md +304 -351
- package/pipeline/skills/shared/external/swift-concurrency/evals/evals.json +27 -24
- package/pipeline/skills/shared/external/swift-concurrency/references/approachable-concurrency.md +65 -80
- package/pipeline/skills/shared/external/swift-concurrency/references/async-algorithms.md +48 -84
- package/pipeline/skills/shared/external/swift-concurrency/references/bridging-interop.md +134 -79
- package/pipeline/skills/shared/external/swift-concurrency/references/concurrency-patterns.md +145 -167
- package/pipeline/skills/shared/external/swift-concurrency/references/diagnostics.md +62 -50
- package/pipeline/skills/shared/external/swift-concurrency/references/swiftui-concurrency.md +92 -121
- package/pipeline/skills/shared/external/swift-concurrency/references/synchronization-primitives.md +177 -241
- package/pipeline/skills/shared/external/swift-formatstyle/SKILL.md +258 -234
- package/pipeline/skills/shared/external/swift-language/SKILL.md +342 -382
- package/pipeline/skills/shared/external/swift-language/evals/evals.json +24 -27
- package/pipeline/skills/shared/external/swift-language/references/swift-attributes-interop.md +79 -56
- package/pipeline/skills/shared/external/swift-language/references/swift-patterns-extended.md +297 -340
- package/pipeline/skills/shared/external/swift-security/SKILL.md +183 -162
- package/pipeline/skills/shared/external/swift-security/evals/evals.json +25 -25
- package/pipeline/skills/shared/external/swift-security/references/biometric-authentication.md +314 -469
- package/pipeline/skills/shared/external/swift-security/references/certificate-trust.md +411 -476
- package/pipeline/skills/shared/external/swift-security/references/common-anti-patterns.md +260 -530
- package/pipeline/skills/shared/external/swift-security/references/compliance-owasp-mapping.md +270 -477
- package/pipeline/skills/shared/external/swift-security/references/credential-storage-patterns.md +573 -571
- package/pipeline/skills/shared/external/swift-security/references/cryptokit-public-key.md +370 -441
- package/pipeline/skills/shared/external/swift-security/references/cryptokit-symmetric.md +332 -433
- package/pipeline/skills/shared/external/swift-security/references/keychain-access-control.md +346 -468
- package/pipeline/skills/shared/external/swift-security/references/keychain-fundamentals.md +375 -491
- package/pipeline/skills/shared/external/swift-security/references/keychain-item-classes.md +431 -432
- package/pipeline/skills/shared/external/swift-security/references/keychain-sharing.md +328 -425
- package/pipeline/skills/shared/external/swift-security/references/migration-legacy-stores.md +341 -579
- package/pipeline/skills/shared/external/swift-security/references/secure-enclave.md +397 -457
- package/pipeline/skills/shared/external/swift-security/references/testing-security-code.md +354 -614
- package/pipeline/skills/shared/external/swift-testing/SKILL.md +191 -175
- package/pipeline/skills/shared/external/swift-testing/evals/evals.json +26 -24
- package/pipeline/skills/shared/external/swift-testing/references/testing-advanced.md +81 -84
- package/pipeline/skills/shared/external/swift-testing/references/testing-patterns.md +317 -433
- package/pipeline/skills/shared/external/swiftdata/SKILL.md +394 -256
- package/pipeline/skills/shared/external/swiftdata/evals/evals.json +24 -24
- package/pipeline/skills/shared/external/swiftdata/references/core-data-coexistence.md +206 -402
- package/pipeline/skills/shared/external/swiftdata/references/indexing.md +59 -52
- package/pipeline/skills/shared/external/swiftdata/references/predicate-pitfalls.md +57 -33
- package/pipeline/skills/shared/external/swiftdata/references/swiftdata-advanced.md +354 -747
- package/pipeline/skills/shared/external/swiftdata/references/swiftdata-queries.md +300 -508
- package/pipeline/skills/shared/external/swiftlint/SKILL.md +175 -226
- package/pipeline/skills/shared/external/swiftlint/references/adoption-and-configuration.md +141 -208
- package/pipeline/skills/shared/external/swiftlint/references/custom-rules-and-analyze.md +100 -109
- package/pipeline/skills/shared/external/swiftlint/references/plugins-run-scripts-and-integrations.md +159 -179
- package/pipeline/skills/shared/external/swiftlint/references/rule-reference.md +383 -18
- package/pipeline/skills/shared/external/swiftlint/references/rules-suppressions-and-baselines.md +143 -229
- package/pipeline/skills/shared/external/swiftui-animation/SKILL.md +283 -366
- package/pipeline/skills/shared/external/swiftui-animation/references/animation-advanced.md +396 -608
- package/pipeline/skills/shared/external/swiftui-animation/references/core-animation-bridge.md +336 -385
- package/pipeline/skills/shared/external/swiftui-gestures/SKILL.md +239 -349
- package/pipeline/skills/shared/external/swiftui-gestures/references/gesture-patterns.md +228 -310
- package/pipeline/skills/shared/external/swiftui-layout-components/SKILL.md +260 -249
- package/pipeline/skills/shared/external/swiftui-layout-components/references/form.md +92 -74
- package/pipeline/skills/shared/external/swiftui-layout-components/references/grids.md +112 -177
- package/pipeline/skills/shared/external/swiftui-layout-components/references/list.md +61 -64
- package/pipeline/skills/shared/external/swiftui-layout-components/references/scrollview.md +94 -134
- package/pipeline/skills/shared/external/swiftui-liquid-glass/SKILL.md +193 -225
- package/pipeline/skills/shared/external/swiftui-liquid-glass/references/liquid-glass.md +173 -327
- package/pipeline/skills/shared/external/swiftui-navigation/SKILL.md +201 -168
- package/pipeline/skills/shared/external/swiftui-navigation/references/deeplinks.md +127 -150
- package/pipeline/skills/shared/external/swiftui-navigation/references/navigationstack.md +134 -133
- package/pipeline/skills/shared/external/swiftui-navigation/references/sheets.md +152 -117
- package/pipeline/skills/shared/external/swiftui-navigation/references/tabview.md +111 -138
- package/pipeline/skills/shared/external/swiftui-patterns/SKILL.md +316 -252
- package/pipeline/skills/shared/external/swiftui-patterns/references/architecture-patterns.md +341 -332
- package/pipeline/skills/shared/external/swiftui-patterns/references/deprecated-migration.md +547 -854
- package/pipeline/skills/shared/external/swiftui-patterns/references/design-polish.md +485 -537
- package/pipeline/skills/shared/external/swiftui-patterns/references/platform-and-sharing.md +417 -499
- package/pipeline/skills/shared/external/swiftui-performance/SKILL.md +213 -376
- package/pipeline/skills/shared/external/swiftui-performance/references/demystify-swiftui-performance-wwdc23.md +86 -175
- package/pipeline/skills/shared/external/swiftui-performance/references/optimizing-swiftui-performance-instruments.md +89 -195
- package/pipeline/skills/shared/external/swiftui-performance/references/understanding-hangs-in-your-app.md +95 -182
- package/pipeline/skills/shared/external/swiftui-performance/references/understanding-improving-swiftui-performance.md +71 -149
- package/pipeline/skills/shared/external/swiftui-performance/references/wwdc-session-sources.md +21 -27
- package/pipeline/skills/shared/external/swiftui-uikit-interop/SKILL.md +303 -295
- package/pipeline/skills/shared/external/swiftui-uikit-interop/references/hosting-migration.md +204 -387
- package/pipeline/skills/shared/external/swiftui-uikit-interop/references/representable-recipes.md +469 -683
- package/pipeline/skills/shared/external/swiftui-webkit/SKILL.md +140 -186
- package/pipeline/skills/shared/external/swiftui-webkit/references/loading-and-observation.md +75 -86
- package/pipeline/skills/shared/external/swiftui-webkit/references/local-content-and-custom-schemes.md +63 -60
- package/pipeline/skills/shared/external/swiftui-webkit/references/migration-and-fallbacks.md +69 -137
- package/pipeline/skills/shared/external/swiftui-webkit/references/navigation-and-javascript.md +95 -67
- package/pipeline/skills/shared/external/tipkit/SKILL.md +220 -335
- package/pipeline/skills/shared/external/tipkit/references/tipkit-patterns.md +356 -494
- package/pipeline/skills/shared/external/vision-framework/SKILL.md +260 -375
- package/pipeline/skills/shared/external/vision-framework/references/vision-requests.md +393 -515
- package/pipeline/skills/shared/external/vision-framework/references/visionkit-scanner.md +363 -539
- package/pipeline/skills/shared/external/weatherkit/SKILL.md +160 -315
- package/pipeline/skills/shared/external/weatherkit/references/weatherkit-patterns.md +288 -407
- package/pipeline/skills/shared/external/widgetkit/SKILL.md +224 -288
- package/pipeline/skills/shared/external/widgetkit/references/widgetkit-advanced.md +416 -719
- package/pipeline/skills/shared/external/NOTICE-swift-ios-skills.md +0 -39
|
@@ -1,496 +1,492 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: swift-codable
|
|
3
|
-
description: "
|
|
3
|
+
description: "Codable JSON and property-list models: JSONDecoder, JSONEncoder, CodingKeys, custom init(from:)/encode(to:), nested containers, date, data and key strategies, heterogeneous and lossy arrays, missing-key defaults. Use when parsing API responses, remapping keys, flattening nested JSON, choosing decoding strategies, or wiring Codable into URLSession, SwiftData or UserDefaults."
|
|
4
4
|
metadata:
|
|
5
|
-
source:
|
|
5
|
+
source: multi-agent-pipeline
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Swift Codable
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
- [Heterogeneous Arrays](#heterogeneous-arrays)
|
|
20
|
-
- [Date Decoding Strategies](#date-decoding-strategies)
|
|
21
|
-
- [Data and Key Strategies](#data-and-key-strategies)
|
|
22
|
-
- [Lossy Array Decoding](#lossy-array-decoding)
|
|
23
|
-
- [Single Value Containers](#single-value-containers)
|
|
24
|
-
- [Default Values for Missing Keys](#default-values-for-missing-keys)
|
|
25
|
-
- [Encoder and Decoder Configuration](#encoder-and-decoder-configuration)
|
|
26
|
-
- [Codable with URLSession](#codable-with-urlsession)
|
|
27
|
-
- [Codable with SwiftData](#codable-with-swiftdata)
|
|
28
|
-
- [Codable with UserDefaults](#codable-with-userdefaults)
|
|
29
|
-
- [Common Mistakes](#common-mistakes)
|
|
30
|
-
- [Review Checklist](#review-checklist)
|
|
31
|
-
- [References](#references)
|
|
32
|
-
|
|
33
|
-
## Basic Conformance
|
|
34
|
-
|
|
35
|
-
When all stored properties are themselves `Codable`, the compiler synthesizes
|
|
36
|
-
conformance automatically:
|
|
10
|
+
How to turn JSON and property lists into Swift values and back, for Swift 6.3
|
|
11
|
+
and iOS 26. The compiler writes most of the code; this skill is about the
|
|
12
|
+
places where it cannot, and about the boundaries where Codable meets
|
|
13
|
+
networking, SwiftData and `@AppStorage`.
|
|
14
|
+
|
|
15
|
+
## Basic conformance
|
|
16
|
+
|
|
17
|
+
When every stored property is itself Codable, the compiler synthesizes the
|
|
18
|
+
whole conformance.
|
|
37
19
|
|
|
38
20
|
```swift
|
|
39
|
-
struct
|
|
21
|
+
struct Recipe: Codable {
|
|
40
22
|
let id: Int
|
|
41
|
-
let
|
|
42
|
-
let
|
|
43
|
-
let isVerified: Bool
|
|
23
|
+
let title: String
|
|
24
|
+
let servings: Int
|
|
44
25
|
}
|
|
45
26
|
|
|
46
|
-
let
|
|
47
|
-
let
|
|
27
|
+
let recipe = try JSONDecoder().decode(Recipe.self, from: payload)
|
|
28
|
+
let bytes = try JSONEncoder().encode(recipe)
|
|
48
29
|
```
|
|
49
30
|
|
|
50
|
-
|
|
51
|
-
|
|
31
|
+
Pick the narrowest protocol:
|
|
32
|
+
|
|
33
|
+
- `Decodable` for data you only read, such as most API responses.
|
|
34
|
+
- `Encodable` for data you only send.
|
|
35
|
+
- `Codable` only when the type really travels both ways.
|
|
52
36
|
|
|
53
37
|
## Custom CodingKeys
|
|
54
38
|
|
|
55
|
-
|
|
56
|
-
enum:
|
|
39
|
+
A `CodingKeys` enum renames keys and still leaves decoding to the compiler.
|
|
57
40
|
|
|
58
41
|
```swift
|
|
59
|
-
struct
|
|
42
|
+
struct Author: Decodable {
|
|
60
43
|
let id: Int
|
|
61
|
-
let
|
|
62
|
-
let
|
|
63
|
-
let priceInCents: Int
|
|
44
|
+
let penName: String
|
|
45
|
+
let avatarURL: URL
|
|
64
46
|
|
|
65
47
|
enum CodingKeys: String, CodingKey {
|
|
66
48
|
case id
|
|
67
|
-
case
|
|
68
|
-
case
|
|
69
|
-
case priceInCents = "price_in_cents"
|
|
49
|
+
case penName = "pen_name"
|
|
50
|
+
case avatarURL = "avatar_url"
|
|
70
51
|
}
|
|
71
52
|
}
|
|
72
53
|
```
|
|
73
54
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
55
|
+
Once the enum exists, it is the complete list of coded properties. A property
|
|
56
|
+
left out is not decoded or encoded at all, so it must have a default value or
|
|
57
|
+
be computed some other way.
|
|
77
58
|
|
|
78
|
-
## Custom
|
|
59
|
+
## Custom decoding and encoding
|
|
79
60
|
|
|
80
|
-
|
|
81
|
-
|
|
61
|
+
Write `init(from:)` and `encode(to:)` when a value needs converting on the
|
|
62
|
+
way in or out.
|
|
82
63
|
|
|
83
64
|
```swift
|
|
84
|
-
struct
|
|
85
|
-
let
|
|
86
|
-
let
|
|
87
|
-
let
|
|
65
|
+
struct Shipment: Codable {
|
|
66
|
+
let reference: String
|
|
67
|
+
let dispatchedAt: Date
|
|
68
|
+
let labels: [String]
|
|
88
69
|
|
|
89
70
|
enum CodingKeys: String, CodingKey {
|
|
90
|
-
case
|
|
71
|
+
case reference, dispatchedAt = "dispatched_epoch", labels
|
|
91
72
|
}
|
|
92
73
|
|
|
93
|
-
init(from decoder: Decoder) throws {
|
|
94
|
-
let
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
// Default to empty array when key is missing
|
|
100
|
-
tags = try container.decodeIfPresent([String].self, forKey: .tags) ?? []
|
|
74
|
+
init(from decoder: any Decoder) throws {
|
|
75
|
+
let fields = try decoder.container(keyedBy: CodingKeys.self)
|
|
76
|
+
reference = try fields.decode(String.self, forKey: .reference)
|
|
77
|
+
let seconds = try fields.decode(Double.self, forKey: .dispatchedAt)
|
|
78
|
+
dispatchedAt = Date(timeIntervalSince1970: seconds)
|
|
79
|
+
labels = try fields.decodeIfPresent([String].self, forKey: .labels) ?? []
|
|
101
80
|
}
|
|
102
81
|
|
|
103
|
-
func encode(to encoder: Encoder) throws {
|
|
104
|
-
var
|
|
105
|
-
try
|
|
106
|
-
try
|
|
107
|
-
try
|
|
82
|
+
func encode(to encoder: any Encoder) throws {
|
|
83
|
+
var fields = encoder.container(keyedBy: CodingKeys.self)
|
|
84
|
+
try fields.encode(reference, forKey: .reference)
|
|
85
|
+
try fields.encode(dispatchedAt.timeIntervalSince1970, forKey: .dispatchedAt)
|
|
86
|
+
try fields.encode(labels, forKey: .labels)
|
|
108
87
|
}
|
|
109
88
|
}
|
|
110
89
|
```
|
|
111
90
|
|
|
112
|
-
|
|
91
|
+
Doing the conversion here, instead of patching values after decoding, keeps
|
|
92
|
+
invalid instances from ever existing.
|
|
113
93
|
|
|
114
|
-
|
|
94
|
+
## Nested and flattened containers
|
|
95
|
+
|
|
96
|
+
JSON often nests what the app wants flat. `nestedContainer(keyedBy:forKey:)`
|
|
97
|
+
reaches into the inner object with its own key enum.
|
|
115
98
|
|
|
116
99
|
```swift
|
|
117
|
-
//
|
|
118
|
-
struct
|
|
119
|
-
let
|
|
100
|
+
// { "name": "Harbour Cafe", "geo": { "latitude": 41.0, "longitude": 29.0 } }
|
|
101
|
+
struct Venue: Decodable {
|
|
102
|
+
let name: String
|
|
120
103
|
let latitude: Double
|
|
121
104
|
let longitude: Double
|
|
122
105
|
|
|
123
|
-
enum
|
|
124
|
-
enum
|
|
106
|
+
enum OuterKeys: String, CodingKey { case name, geo }
|
|
107
|
+
enum GeoKeys: String, CodingKey { case latitude, longitude }
|
|
125
108
|
|
|
126
|
-
init(from decoder: Decoder) throws {
|
|
127
|
-
let
|
|
128
|
-
|
|
129
|
-
let
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
longitude = try location.decode(Double.self, forKey: .lng)
|
|
109
|
+
init(from decoder: any Decoder) throws {
|
|
110
|
+
let outer = try decoder.container(keyedBy: OuterKeys.self)
|
|
111
|
+
name = try outer.decode(String.self, forKey: .name)
|
|
112
|
+
let geo = try outer.nestedContainer(keyedBy: GeoKeys.self, forKey: .geo)
|
|
113
|
+
latitude = try geo.decode(Double.self, forKey: .latitude)
|
|
114
|
+
longitude = try geo.decode(Double.self, forKey: .longitude)
|
|
133
115
|
}
|
|
134
116
|
}
|
|
135
117
|
```
|
|
136
118
|
|
|
137
|
-
|
|
138
|
-
|
|
119
|
+
Deeper structures chain further `nestedContainer` calls. An array inside a
|
|
120
|
+
keyed object comes from `nestedUnkeyedContainer(forKey:)`.
|
|
139
121
|
|
|
140
|
-
## Heterogeneous
|
|
122
|
+
## Heterogeneous arrays
|
|
141
123
|
|
|
142
|
-
|
|
124
|
+
For a list whose elements differ by a `kind` field, model the element as an
|
|
125
|
+
enum with associated values. Read the discriminator first, then the payload it
|
|
126
|
+
selects.
|
|
143
127
|
|
|
144
128
|
```swift
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
case
|
|
148
|
-
case
|
|
149
|
-
|
|
150
|
-
enum
|
|
151
|
-
|
|
152
|
-
init(from decoder: Decoder) throws {
|
|
153
|
-
let
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
case "
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
case "image":
|
|
160
|
-
let url = try container.decode(URL.self, forKey: .url)
|
|
161
|
-
self = .image(url)
|
|
162
|
-
default:
|
|
129
|
+
enum FeedItem: Decodable {
|
|
130
|
+
case note(String)
|
|
131
|
+
case photo(URL)
|
|
132
|
+
case poll(options: [String])
|
|
133
|
+
|
|
134
|
+
enum Keys: String, CodingKey { case kind, text, url, options }
|
|
135
|
+
|
|
136
|
+
init(from decoder: any Decoder) throws {
|
|
137
|
+
let item = try decoder.container(keyedBy: Keys.self)
|
|
138
|
+
switch try item.decode(String.self, forKey: .kind) {
|
|
139
|
+
case "note": self = .note(try item.decode(String.self, forKey: .text))
|
|
140
|
+
case "photo": self = .photo(try item.decode(URL.self, forKey: .url))
|
|
141
|
+
case "poll": self = .poll(options: try item.decode([String].self, forKey: .options))
|
|
142
|
+
case let other:
|
|
163
143
|
throw DecodingError.dataCorruptedError(
|
|
164
|
-
forKey: .
|
|
165
|
-
debugDescription: "Unknown type: \(type)")
|
|
144
|
+
forKey: .kind, in: item, debugDescription: "No FeedItem kind named \(other)")
|
|
166
145
|
}
|
|
167
146
|
}
|
|
168
147
|
}
|
|
169
148
|
|
|
170
|
-
let
|
|
149
|
+
let feed = try JSONDecoder().decode([FeedItem].self, from: payload)
|
|
171
150
|
```
|
|
172
151
|
|
|
173
|
-
## Date
|
|
152
|
+
## Date decoding strategies
|
|
174
153
|
|
|
175
|
-
|
|
154
|
+
Set the strategy on the decoder to match what the server sends.
|
|
176
155
|
|
|
177
156
|
```swift
|
|
178
|
-
let
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
let
|
|
196
|
-
let
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
157
|
+
let reader = JSONDecoder()
|
|
158
|
+
|
|
159
|
+
reader.dateDecodingStrategy = .iso8601 // "2025-11-02T08:15:00Z"
|
|
160
|
+
reader.dateDecodingStrategy = .secondsSince1970 // 1762071300
|
|
161
|
+
|
|
162
|
+
let dayFormat = DateFormatter()
|
|
163
|
+
dayFormat.dateFormat = "dd.MM.yyyy"
|
|
164
|
+
dayFormat.locale = Locale(identifier: "en_US_POSIX")
|
|
165
|
+
dayFormat.timeZone = TimeZone(secondsFromGMT: 0)
|
|
166
|
+
reader.dateDecodingStrategy = .formatted(dayFormat) // "02.11.2025"
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
When one field arrives in more than one shape, decode it by hand:
|
|
170
|
+
|
|
171
|
+
```swift
|
|
172
|
+
reader.dateDecodingStrategy = .custom { decoder in
|
|
173
|
+
let raw = try decoder.singleValueContainer()
|
|
174
|
+
let text = try raw.decode(String.self)
|
|
175
|
+
let withFraction = ISO8601DateFormatter()
|
|
176
|
+
withFraction.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
|
|
177
|
+
if let date = withFraction.date(from: text) ?? ISO8601DateFormatter().date(from: text) {
|
|
178
|
+
return date
|
|
179
|
+
}
|
|
180
|
+
throw DecodingError.dataCorruptedError(in: raw, debugDescription: "Unreadable date: \(text)")
|
|
200
181
|
}
|
|
201
182
|
```
|
|
202
183
|
|
|
203
|
-
|
|
204
|
-
`
|
|
184
|
+
Give the encoder the matching strategy, for example
|
|
185
|
+
`writer.dateEncodingStrategy = .iso8601`, so values round-trip.
|
|
205
186
|
|
|
206
|
-
## Data and
|
|
187
|
+
## Data and key strategies
|
|
207
188
|
|
|
208
189
|
```swift
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
//
|
|
213
|
-
|
|
214
|
-
let encoder = JSONEncoder()
|
|
215
|
-
encoder.dataEncodingStrategy = .base64
|
|
216
|
-
encoder.keyEncodingStrategy = .convertToSnakeCase
|
|
190
|
+
reader.dataDecodingStrategy = .base64
|
|
191
|
+
writer.dataEncodingStrategy = .base64
|
|
192
|
+
|
|
193
|
+
reader.keyDecodingStrategy = .convertFromSnakeCase // "first_name" -> firstName
|
|
194
|
+
writer.keyEncodingStrategy = .convertToSnakeCase
|
|
217
195
|
```
|
|
218
196
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
197
|
+
The snake-case strategies are mechanical. They split on underscores and
|
|
198
|
+
capitalize each following word, nothing more, so:
|
|
199
|
+
|
|
200
|
+
| JSON key | Property name produced |
|
|
201
|
+
| --- | --- |
|
|
202
|
+
| `avatar_url` | `avatarUrl` |
|
|
203
|
+
| `base_uri` | `baseUri` |
|
|
204
|
+
| `owner_id` | `ownerId` |
|
|
224
205
|
|
|
225
|
-
|
|
206
|
+
A model that spells those `avatarURL`, `baseURI` or `ownerID` needs explicit
|
|
207
|
+
`CodingKeys` for exactly those properties. The strategy still handles every
|
|
208
|
+
ordinary key.
|
|
226
209
|
|
|
227
|
-
|
|
228
|
-
|
|
210
|
+
## Lossy array decoding
|
|
211
|
+
|
|
212
|
+
By default a single bad element throws, and the whole array is lost. When a
|
|
213
|
+
feed is known to be unreliable, wrap each element so a failure becomes `nil`
|
|
214
|
+
and the container still moves on:
|
|
229
215
|
|
|
230
216
|
```swift
|
|
231
|
-
struct
|
|
232
|
-
let
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
217
|
+
struct TolerantList<Element: Decodable>: Decodable {
|
|
218
|
+
let items: [Element]
|
|
219
|
+
|
|
220
|
+
private struct Slot: Decodable {
|
|
221
|
+
let value: Element?
|
|
222
|
+
init(from decoder: any Decoder) throws {
|
|
223
|
+
value = try? Element(from: decoder)
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
init(from decoder: any Decoder) throws {
|
|
228
|
+
var list = try decoder.unkeyedContainer()
|
|
229
|
+
var kept: [Element] = []
|
|
230
|
+
while !list.isAtEnd {
|
|
231
|
+
if let value = try list.decode(Slot.self).value {
|
|
232
|
+
kept.append(value)
|
|
242
233
|
}
|
|
243
234
|
}
|
|
244
|
-
|
|
235
|
+
items = kept
|
|
245
236
|
}
|
|
246
237
|
}
|
|
247
|
-
private struct AnyCodableValue: Decodable {}
|
|
248
238
|
```
|
|
249
239
|
|
|
250
|
-
|
|
240
|
+
Decoding `Slot` always succeeds, so the unkeyed container always advances.
|
|
241
|
+
A skip trick that decodes an empty placeholder struct only works when the bad
|
|
242
|
+
element is a JSON object; a bad string or number would throw again and leave
|
|
243
|
+
the loop stuck on the same index.
|
|
251
244
|
|
|
252
|
-
|
|
245
|
+
## Single value containers
|
|
253
246
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
let rawValue: String
|
|
247
|
+
A wrapper type that should appear in JSON as a bare value uses the single value
|
|
248
|
+
container in both directions.
|
|
257
249
|
|
|
258
|
-
|
|
250
|
+
```swift
|
|
251
|
+
struct OrderNumber: Codable, Hashable {
|
|
252
|
+
let text: String
|
|
259
253
|
|
|
260
|
-
init(from decoder: Decoder) throws {
|
|
261
|
-
|
|
262
|
-
rawValue = try container.decode(String.self)
|
|
254
|
+
init(from decoder: any Decoder) throws {
|
|
255
|
+
text = try decoder.singleValueContainer().decode(String.self)
|
|
263
256
|
}
|
|
264
257
|
|
|
265
|
-
func encode(to encoder: Encoder) throws {
|
|
266
|
-
var
|
|
267
|
-
try
|
|
258
|
+
func encode(to encoder: any Encoder) throws {
|
|
259
|
+
var slot = encoder.singleValueContainer()
|
|
260
|
+
try slot.encode(text)
|
|
268
261
|
}
|
|
269
262
|
}
|
|
270
|
-
//
|
|
263
|
+
// Encodes as "A-1042", not {"text":"A-1042"}.
|
|
271
264
|
```
|
|
272
265
|
|
|
273
|
-
## Default
|
|
266
|
+
## Default values for missing keys
|
|
274
267
|
|
|
275
|
-
|
|
276
|
-
`
|
|
277
|
-
property
|
|
278
|
-
nil-coalescing when a missing or null key should fall back:
|
|
268
|
+
A default on a stored property does not help synthesized decoding. With
|
|
269
|
+
`var language = "en"`, a payload without `language` still throws
|
|
270
|
+
`keyNotFound`. Either make the property optional or decode it yourself:
|
|
279
271
|
|
|
280
272
|
```swift
|
|
281
|
-
struct
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
init(from decoder: Decoder) throws {
|
|
292
|
-
let container = try decoder.container(keyedBy: CodingKeys.self)
|
|
293
|
-
theme = try container.decodeIfPresent(String.self, forKey: .theme) ?? "system"
|
|
294
|
-
fontSize = try container.decodeIfPresent(Int.self, forKey: .fontSize) ?? 16
|
|
295
|
-
notificationsEnabled = try container.decodeIfPresent(
|
|
296
|
-
Bool.self, forKey: .notificationsEnabled) ?? true
|
|
273
|
+
struct ReaderSettings: Codable {
|
|
274
|
+
var language: String
|
|
275
|
+
var textSize: Int
|
|
276
|
+
var hyphenation: Bool
|
|
277
|
+
|
|
278
|
+
init(from decoder: any Decoder) throws {
|
|
279
|
+
let fields = try decoder.container(keyedBy: CodingKeys.self)
|
|
280
|
+
language = try fields.decodeIfPresent(String.self, forKey: .language) ?? "en"
|
|
281
|
+
textSize = try fields.decodeIfPresent(Int.self, forKey: .textSize) ?? 17
|
|
282
|
+
hyphenation = try fields.decodeIfPresent(Bool.self, forKey: .hyphenation) ?? false
|
|
297
283
|
}
|
|
298
284
|
}
|
|
299
285
|
```
|
|
300
286
|
|
|
301
|
-
|
|
287
|
+
`decodeIfPresent` returns `nil` for a key that is missing or `null`.
|
|
288
|
+
|
|
289
|
+
## Encoder and decoder configuration
|
|
302
290
|
|
|
303
291
|
```swift
|
|
304
|
-
let
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
let decoder = JSONDecoder()
|
|
308
|
-
// Non-conforming floats (NaN, Infinity are not valid JSON)
|
|
309
|
-
encoder.nonConformingFloatEncodingStrategy = .convertToString(
|
|
310
|
-
positiveInfinity: "Infinity", negativeInfinity: "-Infinity", nan: "NaN")
|
|
311
|
-
decoder.nonConformingFloatDecodingStrategy = .convertFromString(
|
|
312
|
-
positiveInfinity: "Infinity", negativeInfinity: "-Infinity", nan: "NaN")
|
|
292
|
+
let writer = JSONEncoder()
|
|
293
|
+
writer.outputFormatting = [.prettyPrinted, .sortedKeys, .withoutEscapingSlashes]
|
|
313
294
|
```
|
|
314
295
|
|
|
315
|
-
|
|
296
|
+
`.sortedKeys` makes output stable, which is what snapshot and golden-file tests
|
|
297
|
+
need.
|
|
298
|
+
|
|
299
|
+
JSON has no NaN or infinity. If a `Double` can hold them, map them to strings
|
|
300
|
+
explicitly:
|
|
316
301
|
|
|
317
302
|
```swift
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
303
|
+
writer.nonConformingFloatEncodingStrategy = .convertToString(
|
|
304
|
+
positiveInfinity: "inf", negativeInfinity: "-inf", nan: "nan")
|
|
305
|
+
reader.nonConformingFloatDecodingStrategy = .convertFromString(
|
|
306
|
+
positiveInfinity: "inf", negativeInfinity: "-inf", nan: "nan")
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
### PropertyListEncoder and PropertyListDecoder
|
|
310
|
+
|
|
311
|
+
The same models work with property lists:
|
|
312
|
+
|
|
313
|
+
```swift
|
|
314
|
+
let plistWriter = PropertyListEncoder()
|
|
315
|
+
plistWriter.outputFormat = .binary // or .xml when a human will read it
|
|
316
|
+
let stored = try plistWriter.encode(settings)
|
|
317
|
+
let restored = try PropertyListDecoder().decode(ReaderSettings.self, from: stored)
|
|
322
318
|
```
|
|
323
319
|
|
|
324
320
|
## Codable with URLSession
|
|
325
321
|
|
|
322
|
+
Check the HTTP status before decoding; a 404 body decoded as your model yields
|
|
323
|
+
a confusing error.
|
|
324
|
+
|
|
326
325
|
```swift
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
let
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
return try
|
|
326
|
+
enum FetchFailure: Error { case badStatus(Int), notHTTP }
|
|
327
|
+
|
|
328
|
+
func fetchAuthors(from endpoint: URL) async throws -> [Author] {
|
|
329
|
+
let (body, reply) = try await URLSession.shared.data(from: endpoint)
|
|
330
|
+
guard let http = reply as? HTTPURLResponse else { throw FetchFailure.notHTTP }
|
|
331
|
+
guard (200...299).contains(http.statusCode) else { throw FetchFailure.badStatus(http.statusCode) }
|
|
332
|
+
|
|
333
|
+
let reader = JSONDecoder()
|
|
334
|
+
reader.keyDecodingStrategy = .convertFromSnakeCase // CodingKeys still cover URL, URI, ID names
|
|
335
|
+
reader.dateDecodingStrategy = .iso8601
|
|
336
|
+
return try reader.decode([Author].self, from: body)
|
|
338
337
|
}
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
### Generic response envelope
|
|
341
|
+
|
|
342
|
+
```swift
|
|
343
|
+
struct Envelope<Payload: Decodable>: Decodable {
|
|
344
|
+
let data: Payload
|
|
345
|
+
let paging: Paging?
|
|
339
346
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
let meta: Meta?
|
|
345
|
-
struct Meta: Decodable { let page: Int; let totalPages: Int }
|
|
347
|
+
struct Paging: Decodable {
|
|
348
|
+
let page: Int
|
|
349
|
+
let pageCount: Int
|
|
350
|
+
}
|
|
346
351
|
}
|
|
347
352
|
|
|
348
|
-
func
|
|
349
|
-
let
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
return try
|
|
353
|
+
func unwrap<Payload: Decodable>(_ type: Payload.Type, from body: Data) throws -> Payload {
|
|
354
|
+
let reader = JSONDecoder()
|
|
355
|
+
reader.keyDecodingStrategy = .convertFromSnakeCase
|
|
356
|
+
reader.dateDecodingStrategy = .iso8601
|
|
357
|
+
return try reader.decode(Envelope<Payload>.self, from: body).data
|
|
353
358
|
}
|
|
354
359
|
```
|
|
355
360
|
|
|
361
|
+
The helper builds its own decoder. A decoder configured inside another function
|
|
362
|
+
is not in scope here, and code that refers to it will not compile.
|
|
363
|
+
|
|
356
364
|
## Codable with SwiftData
|
|
357
365
|
|
|
358
|
-
SwiftData
|
|
359
|
-
types
|
|
360
|
-
|
|
366
|
+
SwiftData stores the noncomputed stored properties of an `@Model` class when
|
|
367
|
+
their types are supported, and Codable structs and enums are supported. Use
|
|
368
|
+
them as typed properties directly:
|
|
361
369
|
|
|
362
370
|
```swift
|
|
363
|
-
struct
|
|
371
|
+
struct PostalAddress: Codable {
|
|
364
372
|
var street: String
|
|
365
373
|
var city: String
|
|
366
|
-
var
|
|
374
|
+
var postcode: String
|
|
367
375
|
}
|
|
368
376
|
|
|
369
|
-
@Model
|
|
377
|
+
@Model
|
|
378
|
+
final class Customer {
|
|
370
379
|
var name: String
|
|
371
|
-
var
|
|
372
|
-
|
|
373
|
-
|
|
380
|
+
var billing: PostalAddress?
|
|
381
|
+
|
|
382
|
+
init(name: String, billing: PostalAddress? = nil) {
|
|
383
|
+
self.name = name
|
|
384
|
+
self.billing = billing
|
|
374
385
|
}
|
|
375
386
|
}
|
|
376
387
|
```
|
|
377
388
|
|
|
378
|
-
Do not
|
|
379
|
-
`String` as a
|
|
380
|
-
|
|
389
|
+
Do not suggest `@Attribute(.transformable)`, a JSON `Data` blob or a JSON
|
|
390
|
+
`String` column as a workaround here. Keep the property typed. If a design
|
|
391
|
+
needs something SwiftData cannot store, hand it to the SwiftData skill.
|
|
381
392
|
|
|
382
393
|
## Codable with UserDefaults
|
|
383
394
|
|
|
384
|
-
`@AppStorage` is
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
`String` raw storage so `@AppStorage` binds the typed preference directly:
|
|
395
|
+
`@AppStorage` is for small preferences: `Bool`, numbers, `String`, `URL`,
|
|
396
|
+
`Data`, or a `RawRepresentable` whose raw value is `Int` or `String`. A small
|
|
397
|
+
Codable preference can ride on a JSON string raw value:
|
|
388
398
|
|
|
389
399
|
```swift
|
|
390
|
-
struct
|
|
391
|
-
var
|
|
392
|
-
var
|
|
400
|
+
struct PlaybackPrefs: Codable {
|
|
401
|
+
var speed: Double = 1.0
|
|
402
|
+
var skipSilence = false
|
|
403
|
+
|
|
404
|
+
init() {}
|
|
405
|
+
|
|
406
|
+
enum CodingKeys: String, CodingKey { case speed, skipSilence }
|
|
407
|
+
|
|
408
|
+
init(from decoder: any Decoder) throws {
|
|
409
|
+
let fields = try decoder.container(keyedBy: CodingKeys.self)
|
|
410
|
+
speed = try fields.decodeIfPresent(Double.self, forKey: .speed) ?? 1.0
|
|
411
|
+
skipSilence = try fields.decodeIfPresent(Bool.self, forKey: .skipSilence) ?? false
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
func encode(to encoder: any Encoder) throws {
|
|
415
|
+
var fields = encoder.container(keyedBy: CodingKeys.self)
|
|
416
|
+
try fields.encode(speed, forKey: .speed)
|
|
417
|
+
try fields.encode(skipSilence, forKey: .skipSilence)
|
|
418
|
+
}
|
|
393
419
|
}
|
|
394
420
|
|
|
395
|
-
extension
|
|
421
|
+
extension PlaybackPrefs: RawRepresentable {
|
|
396
422
|
init?(rawValue: String) {
|
|
397
|
-
guard let
|
|
398
|
-
let
|
|
399
|
-
|
|
400
|
-
self = decoded
|
|
423
|
+
guard let bytes = rawValue.data(using: .utf8),
|
|
424
|
+
let value = try? JSONDecoder().decode(PlaybackPrefs.self, from: bytes) else { return nil }
|
|
425
|
+
self = value
|
|
401
426
|
}
|
|
427
|
+
|
|
402
428
|
var rawValue: String {
|
|
403
|
-
guard let
|
|
404
|
-
let
|
|
405
|
-
|
|
406
|
-
return string
|
|
429
|
+
guard let bytes = try? JSONEncoder().encode(self),
|
|
430
|
+
let text = String(data: bytes, encoding: .utf8) else { return "{}" }
|
|
431
|
+
return text
|
|
407
432
|
}
|
|
408
433
|
}
|
|
409
434
|
|
|
410
|
-
struct
|
|
411
|
-
@AppStorage("
|
|
435
|
+
struct PlaybackSettingsView: View {
|
|
436
|
+
@AppStorage("playbackPrefs") private var prefs = PlaybackPrefs()
|
|
437
|
+
|
|
412
438
|
var body: some View {
|
|
413
|
-
Toggle("
|
|
439
|
+
Toggle("Skip silence", isOn: $prefs.skipSilence)
|
|
414
440
|
}
|
|
415
441
|
}
|
|
416
442
|
```
|
|
417
443
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
struct APIResponse: Codable { let id: Int; let message: String }
|
|
457
|
-
// DO -- use Decodable for read-only API responses
|
|
458
|
-
struct APIResponse: Decodable { let id: Int; let message: String }
|
|
459
|
-
```
|
|
460
|
-
|
|
461
|
-
**6. Manual CodingKeys for simple snake_case APIs:**
|
|
462
|
-
```swift
|
|
463
|
-
// DON'T -- verbose boilerplate for every model
|
|
464
|
-
enum CodingKeys: String, CodingKey {
|
|
465
|
-
case userName = "user_name"
|
|
466
|
-
case avatarUrl = "avatar_url"
|
|
467
|
-
}
|
|
468
|
-
// DO -- configure once on the decoder for simple cases
|
|
469
|
-
decoder.keyDecodingStrategy = .convertFromSnakeCase
|
|
470
|
-
// Keep CodingKeys for `imageURL`, `baseURI`, `userID`, and similar names.
|
|
471
|
-
```
|
|
472
|
-
|
|
473
|
-
## Review Checklist
|
|
474
|
-
|
|
475
|
-
- [ ] Types conform to `Decodable` only when encoding is not needed
|
|
476
|
-
- [ ] `decodeIfPresent` used with defaults for optional or missing keys
|
|
477
|
-
- [ ] `keyDecodingStrategy = .convertFromSnakeCase` used for simple snake_case APIs, with CodingKeys retained for acronym spellings
|
|
478
|
-
- [ ] `dateDecodingStrategy` matches the API date format
|
|
479
|
-
- [ ] Arrays of unreliable data use lossy decoding to skip invalid elements
|
|
480
|
-
- [ ] Custom `init(from:)` validates and transforms data instead of post-decode fixups
|
|
481
|
-
- [ ] `JSONEncoder.outputFormatting` includes `.sortedKeys` for deterministic test output
|
|
482
|
-
- [ ] Wrapper types (UserID, etc.) use `singleValueContainer` for clean JSON
|
|
483
|
-
- [ ] Generic `APIResponse<T>` wrapper used for consistent API envelope handling
|
|
484
|
-
- [ ] No force-unwrapping of decoded values
|
|
485
|
-
- [ ] Persistence boundary is explicit: SwiftData only for compatible noncomputed model properties, `@AppStorage`/UserDefaults only for small primitive or `RawRepresentable` preferences
|
|
444
|
+
Two details make this bridge safe:
|
|
445
|
+
|
|
446
|
+
- `encode(to:)` and `init(from:)` are written out. When a type is both
|
|
447
|
+
`RawRepresentable` and Codable, the standard library's `RawRepresentable`
|
|
448
|
+
defaults would otherwise encode `rawValue`, which encodes `self`, which
|
|
449
|
+
recurses until the stack overflows.
|
|
450
|
+
- Decoding uses `decodeIfPresent` with defaults, so the `"{}"` fallback and
|
|
451
|
+
values stored by an older version with fewer keys still decode. Synthesized
|
|
452
|
+
decoding would ignore the property defaults and fail on `{}`.
|
|
453
|
+
|
|
454
|
+
## Common mistakes
|
|
455
|
+
|
|
456
|
+
1. **`decode` for a key that may be absent.** It throws `keyNotFound`. Use
|
|
457
|
+
`decodeIfPresent` with `??` and a default.
|
|
458
|
+
2. **One bad element sinks the array.** Use a tolerant wrapper such as
|
|
459
|
+
`TolerantList` or decode elements one by one.
|
|
460
|
+
3. **ISO dates with the default strategy.** The default is `.deferredToDate`,
|
|
461
|
+
which expects a number of seconds since 1 January 2001. Set `.iso8601` for
|
|
462
|
+
ISO 8601 strings.
|
|
463
|
+
4. **Force-unwrapping `try?` results.** Bind with `guard let` and handle the
|
|
464
|
+
failure.
|
|
465
|
+
5. **Codable on read-only responses.** Declare `Decodable`.
|
|
466
|
+
6. **Hand-mapping every snake_case key.** Set `.convertFromSnakeCase` once and
|
|
467
|
+
keep `CodingKeys` for the acronym names the strategy cannot produce.
|
|
468
|
+
|
|
469
|
+
## Review checklist
|
|
470
|
+
|
|
471
|
+
- [ ] `Decodable` alone where encoding is not needed
|
|
472
|
+
- [ ] Optional or missing keys use `decodeIfPresent` with defaults
|
|
473
|
+
- [ ] `.convertFromSnakeCase` for plain snake_case APIs; `CodingKeys` for URL, URI, ID style names
|
|
474
|
+
- [ ] Date strategy matches the API's format
|
|
475
|
+
- [ ] Unreliable arrays decode tolerantly
|
|
476
|
+
- [ ] Conversion and validation happen in `init(from:)`, not in fix-ups after decoding
|
|
477
|
+
- [ ] `.sortedKeys` where output is compared in tests
|
|
478
|
+
- [ ] Wrapper types use `singleValueContainer()`
|
|
479
|
+
- [ ] Responses share a generic envelope such as `Envelope<Payload>`
|
|
480
|
+
- [ ] No force unwraps of decoded values
|
|
481
|
+
- [ ] Persistence boundary is explicit: typed Codable properties on SwiftData models, `@AppStorage` only for small primitive or `RawRepresentable` preferences
|
|
486
482
|
|
|
487
483
|
## References
|
|
488
484
|
|
|
489
|
-
- [Codable](https://
|
|
490
|
-
- [JSONDecoder](https://
|
|
491
|
-
- [JSONEncoder](https://
|
|
492
|
-
- [CodingKey](https://
|
|
493
|
-
- [JSONDecoder.KeyDecodingStrategy.convertFromSnakeCase](https://
|
|
494
|
-
- [Encoding and Decoding Custom Types](https://
|
|
495
|
-
- [Using JSON with Custom Types](https://
|
|
496
|
-
- [Preserving your app's model data across launches](https://
|
|
485
|
+
- [Codable](https://developer.apple.com/documentation/swift/codable)
|
|
486
|
+
- [JSONDecoder](https://developer.apple.com/documentation/foundation/jsondecoder)
|
|
487
|
+
- [JSONEncoder](https://developer.apple.com/documentation/foundation/jsonencoder)
|
|
488
|
+
- [CodingKey](https://developer.apple.com/documentation/swift/codingkey)
|
|
489
|
+
- [JSONDecoder.KeyDecodingStrategy.convertFromSnakeCase](https://developer.apple.com/documentation/foundation/jsondecoder/keydecodingstrategy/convertfromsnakecase)
|
|
490
|
+
- [Encoding and Decoding Custom Types](https://developer.apple.com/documentation/foundation/encoding-and-decoding-custom-types)
|
|
491
|
+
- [Using JSON with Custom Types](https://developer.apple.com/documentation/foundation/using-json-with-custom-types)
|
|
492
|
+
- [Preserving your app's model data across launches](https://developer.apple.com/documentation/swiftdata/preserving-your-apps-model-data-across-launches)
|