@mmerterden/multi-agent-pipeline 20.6.0 → 20.8.0
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 +27 -0
- package/LICENSE +0 -10
- package/docs/facts.json +1 -1
- package/manifest.json +328 -329
- package/package.json +2 -2
- package/pipeline/multi-agent-refs/features/design-conformance.md +62 -64
- package/pipeline/scripts/_notices.mjs +12 -1
- package/pipeline/scripts/gen-skills-index.mjs +1 -1
- package/pipeline/skills/.skill-manifest.json +106 -106
- package/pipeline/skills/shared/README.md +71 -71
- package/pipeline/skills/shared/external/agent-introspection-debugging/SKILL.md +1 -0
- 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/android-architecture/SKILL.md +2 -0
- package/pipeline/skills/shared/external/android-performance/SKILL.md +2 -0
- package/pipeline/skills/shared/external/android-security/SKILL.md +2 -0
- 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 +339 -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 +105 -122
- package/pipeline/skills/shared/external/app-store-optimization/references/product-page-variants.md +143 -166
- package/pipeline/skills/shared/external/app-store-review/SKILL.md +307 -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 +333 -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 +265 -381
- package/pipeline/skills/shared/external/authentication/evals/evals.json +25 -25
- package/pipeline/skills/shared/external/authentication/references/keychain-biometric.md +133 -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 +270 -382
- package/pipeline/skills/shared/external/background-processing/evals/evals.json +22 -22
- package/pipeline/skills/shared/external/background-processing/references/background-task-patterns.md +169 -317
- package/pipeline/skills/shared/external/backlog/BACKLOG.md +1 -1
- package/pipeline/skills/shared/external/backlog/SKILL.md +56 -33
- 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/ci-cd-pipelines/SKILL.md +1 -0
- 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/compose-components/SKILL.md +2 -0
- package/pipeline/skills/shared/external/compose-navigation/SKILL.md +3 -2
- package/pipeline/skills/shared/external/compose-testing/SKILL.md +2 -0
- 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 +226 -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 +292 -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/council/SKILL.md +1 -0
- 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/css-modern/SKILL.md +1 -0
- package/pipeline/skills/shared/external/database-patterns/SKILL.md +1 -0
- 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 +230 -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/evidence-github/SKILL.md +2 -0
- package/pipeline/skills/shared/external/evidence-registry/SKILL.md +2 -0
- package/pipeline/skills/shared/external/gradle-kotlin-dsl/SKILL.md +2 -0
- 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/html-semantic/SKILL.md +1 -0
- package/pipeline/skills/shared/external/humanizer/SKILL.md +1 -0
- 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-coding-standard/SKILL.md +1 -0
- 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-module-structure/SKILL.md +1 -0
- 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-security/SKILL.md +2 -0
- 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 +91 -283
- package/pipeline/skills/shared/external/localization-reuse-map/reference/format-and-output.md +119 -151
- package/pipeline/skills/shared/external/localization-reuse-map/reference/publish-and-snapshot.md +60 -90
- package/pipeline/skills/shared/external/localization-reuse-map/reference/sources-and-recipes.md +119 -156
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-artifact.py +726 -787
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-spreadsheet.py +253 -288
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-annotations.py +243 -304
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-legacy-labels.py +88 -104
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/publish-confluence.py +181 -235
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/render-key-shots.py +198 -263
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/render-overlay.py +461 -466
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-legacy-values.py +145 -151
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-new-values.py +123 -141
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/scan-screen-keys.py +146 -157
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/snapshot-resources.sh +22 -19
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/verify-map.py +156 -140
- package/pipeline/skills/shared/external/mapkit-location/SKILL.md +295 -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/nextjs-app-router/SKILL.md +1 -0
- 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/play-store-review/SKILL.md +2 -0
- 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/python-patterns/SKILL.md +1 -0
- package/pipeline/skills/shared/external/react-best-practices/SKILL.md +1 -0
- 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/rest-api-design/SKILL.md +1 -0
- package/pipeline/skills/shared/external/retrofit-networking/SKILL.md +2 -0
- package/pipeline/skills/shared/external/room-database/SKILL.md +2 -0
- package/pipeline/skills/shared/external/search-first/SKILL.md +1 -0
- 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/signal-community/SKILL.md +2 -0
- package/pipeline/skills/shared/external/skill-creator/SKILL.md +80 -41
- package/pipeline/skills/shared/external/skill-creator/audit.md +63 -59
- package/pipeline/skills/shared/external/skill-creator/checklist.md +28 -20
- package/pipeline/skills/shared/external/skill-creator/examples.md +40 -40
- package/pipeline/skills/shared/external/skill-creator/label-check.md +48 -36
- package/pipeline/skills/shared/external/skill-creator/scripts/audit-panel.js +91 -100
- package/pipeline/skills/shared/external/skill-creator/template.md +51 -39
- 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 +298 -242
- 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 +303 -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 +180 -161
- 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 +408 -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 +352 -472
- 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 +396 -457
- package/pipeline/skills/shared/external/swift-security/references/testing-security-code.md +354 -614
- package/pipeline/skills/shared/external/swift-testing/SKILL.md +188 -175
- package/pipeline/skills/shared/external/swift-testing/evals/evals.json +26 -24
- package/pipeline/skills/shared/external/swift-testing/references/testing-advanced.md +80 -84
- package/pipeline/skills/shared/external/swift-testing/references/testing-patterns.md +317 -433
- package/pipeline/skills/shared/external/swiftdata/SKILL.md +392 -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 +193 -168
- package/pipeline/skills/shared/external/swiftui-navigation/references/deeplinks.md +127 -150
- package/pipeline/skills/shared/external/swiftui-navigation/references/navigationstack.md +132 -133
- package/pipeline/skills/shared/external/swiftui-navigation/references/sheets.md +152 -117
- package/pipeline/skills/shared/external/swiftui-navigation/references/tabview.md +106 -140
- 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/tailwind-css/SKILL.md +1 -0
- package/pipeline/skills/shared/external/testing-backend/SKILL.md +1 -0
- 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/typescript-patterns/SKILL.md +1 -0
- 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/vue-composition/SKILL.md +1 -0
- package/pipeline/skills/shared/external/weatherkit/SKILL.md +152 -310
- package/pipeline/skills/shared/external/weatherkit/references/weatherkit-patterns.md +288 -407
- package/pipeline/skills/shared/external/web-accessibility/SKILL.md +1 -0
- package/pipeline/skills/shared/external/web-performance/SKILL.md +1 -0
- package/pipeline/skills/shared/external/web-testing/SKILL.md +1 -0
- package/pipeline/skills/shared/external/widgetkit/SKILL.md +216 -288
- package/pipeline/skills/shared/external/widgetkit/references/widgetkit-advanced.md +414 -719
- package/pipeline/skills/shared/external/NOTICE-swift-ios-skills.md +0 -39
|
@@ -1,490 +1,414 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: ios-networking
|
|
3
|
-
description: "
|
|
3
|
+
description: "URLSession with async/await and structured concurrency on iOS and macOS: HTTP requests, REST API clients, response validation, error handling, retry with backoff, middleware and token refresh, pagination, caching, downloads, uploads, background transfers, WebSockets, NWPathMonitor reachability, Network.framework, ATS. Use when building or reviewing networking code, API clients or data fetching. Not for pinning (swift-security) or Keychain internals."
|
|
4
4
|
metadata:
|
|
5
|
-
source:
|
|
5
|
+
source: multi-agent-pipeline
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# iOS Networking
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
dependencies required -- URLSession covers the vast majority of networking
|
|
13
|
-
needs.
|
|
14
|
-
|
|
15
|
-
## Contents
|
|
16
|
-
|
|
17
|
-
- [Core URLSession async/await](#core-urlsession-asyncawait)
|
|
18
|
-
- [API Client Architecture](#api-client-architecture)
|
|
19
|
-
- [Error Handling](#error-handling)
|
|
20
|
-
- [Pagination](#pagination)
|
|
21
|
-
- [Network Reachability](#network-reachability)
|
|
22
|
-
- [Configuring URLSession](#configuring-urlsession)
|
|
23
|
-
- [App Transport Security (ATS)](#app-transport-security-ats)
|
|
24
|
-
- [Common Mistakes](#common-mistakes)
|
|
25
|
-
- [Review Checklist](#review-checklist)
|
|
26
|
-
- [References](#references)
|
|
10
|
+
URLSession handles nearly every networking job an app has, and it needs no
|
|
11
|
+
third-party package to do it well. Baseline for this skill: iOS 26, Swift 6.3.
|
|
27
12
|
|
|
28
13
|
## Core URLSession async/await
|
|
29
14
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
15
|
+
The async overloads (iOS 15 and later) are the default for foreground work:
|
|
16
|
+
data, upload, download and streaming. The one exception is a background
|
|
17
|
+
session. The system delivers its events after the app was suspended or even
|
|
18
|
+
relaunched, so it keeps the task-plus-delegate API.
|
|
34
19
|
|
|
35
|
-
### Data
|
|
20
|
+
### Data requests
|
|
36
21
|
|
|
37
22
|
```swift
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
request.cachePolicy = .reloadIgnoringLocalCacheData
|
|
48
|
-
|
|
49
|
-
let (data, response) = try await URLSession.shared.data(for: request)
|
|
23
|
+
let (forecast, _) = try await URLSession.shared.data(from: forecastURL)
|
|
24
|
+
|
|
25
|
+
var order = URLRequest(url: ordersURL)
|
|
26
|
+
order.httpMethod = "POST"
|
|
27
|
+
order.addValue("application/json", forHTTPHeaderField: "Content-Type")
|
|
28
|
+
order.httpBody = try JSONEncoder().encode(newOrder)
|
|
29
|
+
order.timeoutInterval = 30
|
|
30
|
+
order.cachePolicy = .reloadIgnoringLocalCacheData
|
|
31
|
+
let (confirmation, reply) = try await session.data(for: order)
|
|
50
32
|
```
|
|
51
33
|
|
|
52
|
-
### Response
|
|
34
|
+
### Response validation
|
|
53
35
|
|
|
54
|
-
|
|
55
|
-
|
|
36
|
+
A 404 or a 503 is not a thrown error. URLSession throws only when the
|
|
37
|
+
transport fails (no route, TLS failure, cancellation), so check the status
|
|
38
|
+
yourself before touching the body:
|
|
56
39
|
|
|
57
40
|
```swift
|
|
58
|
-
guard let
|
|
41
|
+
guard let status = (response as? HTTPURLResponse)?.statusCode else {
|
|
59
42
|
throw NetworkError.invalidResponse
|
|
60
43
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
throw NetworkError.httpError(
|
|
64
|
-
statusCode: httpResponse.statusCode,
|
|
65
|
-
data: data
|
|
66
|
-
)
|
|
44
|
+
if !(200...299).contains(status) {
|
|
45
|
+
throw NetworkError.httpStatus(code: status, body: data, message: nil)
|
|
67
46
|
}
|
|
68
47
|
```
|
|
69
48
|
|
|
70
|
-
###
|
|
49
|
+
### Decoding with Codable
|
|
71
50
|
|
|
72
51
|
```swift
|
|
73
|
-
func
|
|
74
|
-
let (
|
|
75
|
-
|
|
76
|
-
guard let httpResponse = response as? HTTPURLResponse,
|
|
77
|
-
(200..<300).contains(httpResponse.statusCode) else {
|
|
78
|
-
throw NetworkError.invalidResponse
|
|
79
|
-
}
|
|
80
|
-
|
|
52
|
+
func load<Model: Decodable>(_ kind: Model.Type, at address: URL) async throws -> Model {
|
|
53
|
+
let (bytes, reply) = try await session.data(from: address)
|
|
54
|
+
try validate(reply, body: bytes)
|
|
81
55
|
let decoder = JSONDecoder()
|
|
82
56
|
decoder.dateDecodingStrategy = .iso8601
|
|
83
57
|
decoder.keyDecodingStrategy = .convertFromSnakeCase
|
|
84
|
-
return try decoder.decode(
|
|
58
|
+
return try decoder.decode(kind, from: bytes)
|
|
85
59
|
}
|
|
86
60
|
```
|
|
87
61
|
|
|
88
|
-
### Downloads and
|
|
62
|
+
### Downloads and uploads
|
|
89
63
|
|
|
90
|
-
|
|
91
|
-
|
|
64
|
+
`download(for:)` streams the body to a file instead of memory, so use it for
|
|
65
|
+
anything large. The returned file is temporary: move or copy it right away.
|
|
92
66
|
|
|
93
67
|
```swift
|
|
94
|
-
|
|
95
|
-
let
|
|
68
|
+
let (tempFile, _) = try await session.download(for: manualRequest)
|
|
69
|
+
let target = URL.documentsDirectory.appending(path: "manual.pdf")
|
|
70
|
+
try FileManager.default.moveItem(at: tempFile, to: target)
|
|
96
71
|
|
|
97
|
-
|
|
98
|
-
let
|
|
99
|
-
try FileManager.default.moveItem(at: localURL, to: destination)
|
|
72
|
+
let (ack, _) = try await session.upload(for: syncRequest, from: jsonData)
|
|
73
|
+
let (receipt, _) = try await session.upload(for: videoRequest, fromFile: videoURL)
|
|
100
74
|
```
|
|
101
75
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
Background sessions are delegate-driven transfer queues. Use task creation
|
|
106
|
-
APIs such as `downloadTask(with:)` and file-backed `uploadTask(with:fromFile:)`,
|
|
107
|
-
then handle `URLSessionDelegate` / task delegate callbacks. Do not use async
|
|
108
|
-
convenience APIs such as `data(for:)`, `download(for:)`, or `upload(for:)` as
|
|
109
|
-
the durable background-session pattern.
|
|
110
|
-
|
|
111
|
-
```swift
|
|
112
|
-
// Upload data
|
|
113
|
-
let (data, response) = try await URLSession.shared.upload(for: request, from: bodyData)
|
|
76
|
+
A `URLSessionDownloadDelegate` gets the same kind of temporary file and has
|
|
77
|
+
to relocate or open it before `urlSession(_:downloadTask:didFinishDownloadingTo:)` returns.
|
|
114
78
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
79
|
+
A background session is a delegate-driven transfer queue. Its work is
|
|
80
|
+
`downloadTask(with:)` plus `uploadTask(with:fromFile:)` for uploads from disk,
|
|
81
|
+
reported through session and task delegate callbacks. The async conveniences
|
|
82
|
+
`data(for:)`, `download(for:)` and `upload(for:)` are not a durable
|
|
83
|
+
background pattern. See [background and WebSocket guide](references/background-websocket.md).
|
|
118
84
|
|
|
119
85
|
### Streaming with AsyncBytes
|
|
120
86
|
|
|
121
|
-
|
|
122
|
-
|
|
87
|
+
`bytes(for:)` hands you the body as it arrives: useful for progress, for
|
|
88
|
+
newline-delimited JSON and for server-sent events.
|
|
123
89
|
|
|
124
90
|
```swift
|
|
125
|
-
let (
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
// Process each line as it arrives (e.g., SSE stream)
|
|
129
|
-
handleEvent(line)
|
|
91
|
+
let (stream, _) = try await session.bytes(for: request)
|
|
92
|
+
for try await line in stream.lines {
|
|
93
|
+
handle(line)
|
|
130
94
|
}
|
|
131
95
|
```
|
|
132
96
|
|
|
133
|
-
## API
|
|
97
|
+
## API client architecture
|
|
134
98
|
|
|
135
|
-
### Protocol-
|
|
99
|
+
### Protocol-based client
|
|
136
100
|
|
|
137
|
-
|
|
138
|
-
|
|
101
|
+
Put the client behind a protocol. Tests then substitute a double instead of
|
|
102
|
+
mocking URLSession.
|
|
139
103
|
|
|
140
104
|
```swift
|
|
141
105
|
protocol APIClientProtocol: Sendable {
|
|
142
|
-
func fetch<
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
) async throws
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
_ type: T.Type,
|
|
149
|
-
endpoint: Endpoint,
|
|
150
|
-
body: some Encodable & Sendable
|
|
151
|
-
) async throws -> T
|
|
106
|
+
func fetch<Reply: Decodable & Sendable>(_ endpoint: Endpoint, as kind: Reply.Type) async throws -> Reply
|
|
107
|
+
func send<Reply: Decodable & Sendable>(_ endpoint: Endpoint, json payload: some Encodable & Sendable,
|
|
108
|
+
as kind: Reply.Type) async throws -> Reply
|
|
109
|
+
func perform(_ endpoint: Endpoint) async throws
|
|
110
|
+
func upload<Reply: Decodable & Sendable>(_ endpoint: Endpoint, bytes: Data,
|
|
111
|
+
as kind: Reply.Type) async throws -> Reply
|
|
152
112
|
}
|
|
153
|
-
```
|
|
154
113
|
|
|
155
|
-
```swift
|
|
156
114
|
struct Endpoint: Sendable {
|
|
157
|
-
|
|
158
|
-
|
|
115
|
+
enum HTTPMethod: String, Sendable { case get, post, put, patch, delete }
|
|
116
|
+
|
|
117
|
+
var path: String
|
|
118
|
+
var method: HTTPMethod = .get
|
|
159
119
|
var queryItems: [URLQueryItem] = []
|
|
160
120
|
var headers: [String: String] = [:]
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
var mutableComponents = components
|
|
170
|
-
if !queryItems.isEmpty {
|
|
171
|
-
mutableComponents.queryItems = queryItems
|
|
121
|
+
var body: Data? = nil
|
|
122
|
+
var cachePolicy: URLRequest.CachePolicy = .useProtocolCachePolicy
|
|
123
|
+
var timeoutInterval: TimeInterval = 30
|
|
124
|
+
|
|
125
|
+
func urlRequest(relativeTo baseURL: URL) throws -> URLRequest {
|
|
126
|
+
guard var parts = URLComponents(url: baseURL.appending(path: path),
|
|
127
|
+
resolvingAgainstBaseURL: true) else {
|
|
128
|
+
throw NetworkError.invalidURL
|
|
172
129
|
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
130
|
+
if !queryItems.isEmpty { parts.queryItems = queryItems }
|
|
131
|
+
guard let url = parts.url else { throw NetworkError.invalidURL }
|
|
132
|
+
var request = URLRequest(url: url, cachePolicy: cachePolicy, timeoutInterval: timeoutInterval)
|
|
133
|
+
request.httpMethod = method.rawValue.uppercased()
|
|
134
|
+
request.httpBody = body
|
|
135
|
+
request.allHTTPHeaderFields = headers
|
|
136
|
+
return request
|
|
177
137
|
}
|
|
178
138
|
}
|
|
179
139
|
```
|
|
180
140
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
`URLRequest` from the endpoint, applies middleware, executes the request,
|
|
184
|
-
validates the status code, and decodes the result. See
|
|
185
|
-
[references/urlsession-patterns.md](references/urlsession-patterns.md) for the complete `APIClient` implementation
|
|
186
|
-
with convenience methods, request builder, and test setup.
|
|
141
|
+
A bad URL is a thrown `NetworkError.invalidURL`, never a crash; nothing in
|
|
142
|
+
this skill force-unwraps.
|
|
187
143
|
|
|
188
|
-
|
|
189
|
-
of
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
144
|
+
The concrete `APIClient` is built from a base URL, an injected `URLSession`
|
|
145
|
+
and an ordered list of `RequestMiddleware`; it owns a snake_case, ISO 8601
|
|
146
|
+
`JSONDecoder` and `JSONEncoder`. Every method runs
|
|
147
|
+
the same pipeline: build the request, apply middleware, execute, validate,
|
|
148
|
+
decode. The full implementation, a fluent request builder and tests are in
|
|
149
|
+
[URLSession patterns](references/urlsession-patterns.md).
|
|
193
150
|
|
|
194
|
-
|
|
151
|
+
Inject a configured session into production clients rather than reaching for
|
|
152
|
+
`URLSession.shared` inside them. The configuration is where request and
|
|
153
|
+
resource timeouts, cache policy or a `URLCache`, `waitsForConnectivity`,
|
|
154
|
+
cellular and Low Data Mode policy live, plus a delegate when you need auth
|
|
155
|
+
challenges, redirect control, metrics, pinning or background transfers.
|
|
195
156
|
|
|
196
|
-
|
|
197
|
-
and SwiftUI preview support. See [references/lightweight-clients.md](references/lightweight-clients.md) for
|
|
198
|
-
the full pattern (struct of async closures, injected via init).
|
|
157
|
+
### Lightweight closure-based client
|
|
199
158
|
|
|
200
|
-
|
|
159
|
+
MV-style SwiftUI apps often do better with a struct of async closures passed
|
|
160
|
+
in through `init`: trivial to stub in previews and tests. See
|
|
161
|
+
[lightweight clients](references/lightweight-clients.md).
|
|
201
162
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
163
|
+
### Request middleware
|
|
164
|
+
|
|
165
|
+
Middleware rewrites a request before it goes out. Authentication, logging,
|
|
166
|
+
tracing and analytics headers all fit here.
|
|
205
167
|
|
|
206
168
|
```swift
|
|
207
169
|
protocol RequestMiddleware: Sendable {
|
|
208
|
-
func
|
|
170
|
+
func adapt(_ request: inout URLRequest) async throws
|
|
209
171
|
}
|
|
210
|
-
```
|
|
211
172
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
let tokenProvider: @Sendable () async throws -> String
|
|
173
|
+
struct BearerTokenMiddleware: RequestMiddleware {
|
|
174
|
+
let currentToken: @Sendable () async throws -> String
|
|
215
175
|
|
|
216
|
-
func
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
|
|
220
|
-
return request
|
|
176
|
+
func adapt(_ request: inout URLRequest) async throws {
|
|
177
|
+
let token = try await currentToken()
|
|
178
|
+
request.addValue("Bearer " + token, forHTTPHeaderField: "Authorization")
|
|
221
179
|
}
|
|
222
180
|
}
|
|
223
181
|
```
|
|
224
182
|
|
|
225
|
-
### Token
|
|
183
|
+
### Token refresh
|
|
226
184
|
|
|
227
|
-
|
|
185
|
+
On a 401, refresh once and repeat the call once. A second 401 is a real
|
|
186
|
+
failure, not a loop.
|
|
228
187
|
|
|
229
188
|
```swift
|
|
230
|
-
func
|
|
231
|
-
_ type: T.Type,
|
|
232
|
-
endpoint: Endpoint,
|
|
233
|
-
tokenStore: TokenStore
|
|
234
|
-
) async throws -> T {
|
|
189
|
+
func fetchRefreshingOnce<Reply: Decodable & Sendable>(_ endpoint: Endpoint, as kind: Reply.Type) async throws -> Reply {
|
|
235
190
|
do {
|
|
236
|
-
return try await fetch(
|
|
237
|
-
} catch NetworkError.
|
|
238
|
-
try await
|
|
239
|
-
return try await fetch(
|
|
191
|
+
return try await client.fetch(endpoint, as: kind)
|
|
192
|
+
} catch NetworkError.httpStatus(code: 401, _, _) {
|
|
193
|
+
try await credentials.refresh()
|
|
194
|
+
return try await client.fetch(endpoint, as: kind)
|
|
240
195
|
}
|
|
241
196
|
}
|
|
242
197
|
```
|
|
243
198
|
|
|
244
|
-
## Error
|
|
199
|
+
## Error handling
|
|
245
200
|
|
|
246
|
-
### Structured
|
|
201
|
+
### Structured error type
|
|
202
|
+
|
|
203
|
+
One error enum is shared by this file and every reference:
|
|
247
204
|
|
|
248
205
|
```swift
|
|
249
|
-
enum NetworkError: Error,
|
|
206
|
+
enum NetworkError: Error, LocalizedError {
|
|
207
|
+
case invalidURL
|
|
250
208
|
case invalidResponse
|
|
251
|
-
case
|
|
252
|
-
case decodingFailed(Error)
|
|
209
|
+
case httpStatus(code: Int, body: Data, message: String?)
|
|
210
|
+
case decodingFailed(any Error)
|
|
253
211
|
case noConnection
|
|
254
212
|
case timedOut
|
|
255
213
|
case cancelled
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
static func from(_
|
|
259
|
-
switch
|
|
260
|
-
case .notConnectedToInternet, .networkConnectionLost:
|
|
261
|
-
|
|
262
|
-
case .
|
|
263
|
-
|
|
264
|
-
case .cancelled:
|
|
265
|
-
return .cancelled
|
|
266
|
-
default:
|
|
267
|
-
return .httpError(statusCode: -1, data: Data())
|
|
214
|
+
case transport(URLError)
|
|
215
|
+
|
|
216
|
+
static func from(_ error: URLError) -> NetworkError {
|
|
217
|
+
switch error.code {
|
|
218
|
+
case .notConnectedToInternet, .networkConnectionLost: .noConnection
|
|
219
|
+
case .timedOut: .timedOut
|
|
220
|
+
case .cancelled: .cancelled
|
|
221
|
+
default: .transport(error)
|
|
268
222
|
}
|
|
269
223
|
}
|
|
270
224
|
}
|
|
271
225
|
```
|
|
272
226
|
|
|
273
|
-
|
|
227
|
+
`Error` already refines `Sendable`, so the enum is Sendable without saying
|
|
228
|
+
so. Unknown transport failures keep their `URLError` in `.transport` instead
|
|
229
|
+
of posing as a fake HTTP status. `errorDescription` returns `nil` for
|
|
230
|
+
`.cancelled` so cancellation never surfaces as a message; the full
|
|
231
|
+
implementation is in [URLSession patterns](references/urlsession-patterns.md#error-types).
|
|
274
232
|
|
|
275
|
-
|
|
233
|
+
### URLError codes worth handling
|
|
234
|
+
|
|
235
|
+
| Code | What happened | Response |
|
|
276
236
|
|---|---|---|
|
|
277
|
-
| `.notConnectedToInternet` | Device offline |
|
|
278
|
-
| `.networkConnectionLost` |
|
|
279
|
-
| `.timedOut` | Server
|
|
280
|
-
| `.cancelled` | Task was cancelled |
|
|
281
|
-
| `.cannotFindHost` | DNS
|
|
282
|
-
| `.secureConnectionFailed` | TLS handshake failed | Check
|
|
283
|
-
| `.userAuthenticationRequired` |
|
|
237
|
+
| `.notConnectedToInternet` | Device is offline | Offline UI, queue for later |
|
|
238
|
+
| `.networkConnectionLost` | The connection broke partway through | Back off, then try again |
|
|
239
|
+
| `.timedOut` | Server too slow | Retry once, then report |
|
|
240
|
+
| `.cancelled` | Task was cancelled | Do nothing, show nothing |
|
|
241
|
+
| `.cannotFindHost` | DNS lookup failed | Check the host, report |
|
|
242
|
+
| `.secureConnectionFailed` | TLS handshake failed | Check ATS and pinning setup |
|
|
243
|
+
| `.userAuthenticationRequired` | Resource needs credentials | Start sign-in |
|
|
244
|
+
|
|
245
|
+
### Server error bodies
|
|
284
246
|
|
|
285
|
-
|
|
247
|
+
Many APIs return a JSON body with the failure. The client decodes it into
|
|
248
|
+
`message` when it can; a caller can also decode it on demand:
|
|
286
249
|
|
|
287
250
|
```swift
|
|
288
|
-
struct
|
|
289
|
-
let code: String
|
|
290
|
-
let message: String
|
|
291
|
-
}
|
|
251
|
+
struct APIErrorBody: Decodable, Sendable {
|
|
252
|
+
let code: String?
|
|
253
|
+
let message: String?
|
|
292
254
|
|
|
293
|
-
func
|
|
294
|
-
|
|
255
|
+
static func decode(from data: Data) -> APIErrorBody? {
|
|
256
|
+
try? JSONDecoder().decode(APIErrorBody.self, from: data)
|
|
257
|
+
}
|
|
295
258
|
}
|
|
296
259
|
|
|
297
|
-
|
|
298
|
-
catch NetworkError.
|
|
299
|
-
|
|
300
|
-
showError("Server error: \(apiError.message)")
|
|
301
|
-
} else {
|
|
302
|
-
showError("HTTP \(statusCode)")
|
|
303
|
-
}
|
|
260
|
+
do { try await client.perform(archiveEndpoint) }
|
|
261
|
+
catch NetworkError.httpStatus(let status, let body, let message) {
|
|
262
|
+
banner = message ?? APIErrorBody.decode(from: body)?.message ?? "Request failed (\(status))"
|
|
304
263
|
}
|
|
305
264
|
```
|
|
306
265
|
|
|
307
|
-
### Retry with
|
|
266
|
+
### Retry with exponential backoff
|
|
308
267
|
|
|
309
|
-
|
|
310
|
-
|
|
268
|
+
Retry inside structured concurrency so cancellation stops the loop. Never
|
|
269
|
+
retry cancellation or a 4xx, with 429 as the exception; 5xx and transient
|
|
270
|
+
transport errors are fair game.
|
|
311
271
|
|
|
312
272
|
```swift
|
|
313
273
|
func withRetry<T: Sendable>(
|
|
314
274
|
maxAttempts: Int = 3,
|
|
315
275
|
initialDelay: Duration = .seconds(1),
|
|
276
|
+
maxDelay: Duration = .seconds(30),
|
|
277
|
+
shouldRetry: @Sendable (any Error) -> Bool = isTransient,
|
|
316
278
|
operation: @Sendable () async throws -> T
|
|
317
279
|
) async throws -> T {
|
|
318
|
-
|
|
280
|
+
precondition(maxAttempts > 0, "maxAttempts must be at least 1")
|
|
319
281
|
for attempt in 0..<maxAttempts {
|
|
282
|
+
try Task.checkCancellation()
|
|
320
283
|
do {
|
|
321
284
|
return try await operation()
|
|
322
285
|
} catch {
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
(400..<500).contains(code), code != 429 { throw error }
|
|
327
|
-
if attempt < maxAttempts - 1 {
|
|
328
|
-
try await Task.sleep(for: initialDelay * Int(pow(2.0, Double(attempt))))
|
|
329
|
-
}
|
|
286
|
+
guard attempt < maxAttempts - 1, shouldRetry(error) else { throw error }
|
|
287
|
+
let capped = min(initialDelay * (1 << attempt), maxDelay)
|
|
288
|
+
try await Task.sleep(for: capped + capped * Double.random(in: 0...0.1))
|
|
330
289
|
}
|
|
331
290
|
}
|
|
332
|
-
|
|
291
|
+
preconditionFailure("the last attempt always returns or throws")
|
|
333
292
|
}
|
|
334
293
|
```
|
|
335
294
|
|
|
295
|
+
There is no sleep after the last attempt: its error is rethrown at once. The
|
|
296
|
+
`isTransient` predicate is in
|
|
297
|
+
[URLSession patterns](references/urlsession-patterns.md#retries-that-back-off-exponentially).
|
|
298
|
+
|
|
336
299
|
## Pagination
|
|
337
300
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
301
|
+
Model paging as an `AsyncSequence` of pages, cursor-based or offset-based,
|
|
302
|
+
and call `try Task.checkCancellation()` (or test `Task.isCancelled`) before
|
|
303
|
+
every page request. Both paginators are written out in
|
|
304
|
+
[URLSession patterns](references/urlsession-patterns.md#paging-by-cursor).
|
|
342
305
|
|
|
343
|
-
## Network
|
|
306
|
+
## Network reachability
|
|
344
307
|
|
|
345
|
-
Use `NWPathMonitor
|
|
346
|
-
|
|
347
|
-
|
|
308
|
+
Use `NWPathMonitor`, not a third-party Reachability port. On current SDKs the
|
|
309
|
+
monitor is itself an `AsyncSequence`; wrapping `pathUpdateHandler` in your own
|
|
310
|
+
stream is only for older targets or for a custom projection.
|
|
348
311
|
|
|
349
312
|
```swift
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
let monitor = NWPathMonitor()
|
|
354
|
-
|
|
355
|
-
for await path in monitor {
|
|
356
|
-
handle(path.status)
|
|
357
|
-
}
|
|
313
|
+
for await path in NWPathMonitor() {
|
|
314
|
+
isOnline = path.status == .satisfied
|
|
315
|
+
useLowBandwidth = path.isConstrained || path.isExpensive
|
|
358
316
|
}
|
|
359
317
|
```
|
|
360
318
|
|
|
361
|
-
|
|
362
|
-
Mode
|
|
319
|
+
`isExpensive` means cellular or a hotspot; `isConstrained` means Low Data
|
|
320
|
+
Mode. Use them to lower image quality or skip prefetching.
|
|
363
321
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
`NetworkConnection<QUIC
|
|
367
|
-
|
|
322
|
+
Network.framework is for TCP and UDP, listeners, Bonjour, path monitoring and
|
|
323
|
+
WebSocket protocol work. It is not the tool for an ordinary REST API. On
|
|
324
|
+
iOS 26 `NetworkConnection<QUIC>` adds multiplexed streams, and both
|
|
325
|
+
`openStream(...)` and `inboundStreams(...)` are async and throwing. See
|
|
326
|
+
[Network framework guide](references/network-framework.md) and its
|
|
327
|
+
[QUIC stream notes](references/network-framework.md#quic-multiplexed-streams).
|
|
368
328
|
|
|
369
329
|
## Configuring URLSession
|
|
370
330
|
|
|
371
|
-
|
|
372
|
-
|
|
331
|
+
`URLSession.shared` suits a quick one-off call. Anything that ships deserves a
|
|
332
|
+
configured session:
|
|
373
333
|
|
|
374
334
|
```swift
|
|
375
|
-
let
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
335
|
+
let config = URLSessionConfiguration.default
|
|
336
|
+
config.timeoutIntervalForRequest = 30
|
|
337
|
+
config.timeoutIntervalForResource = 300
|
|
338
|
+
config.waitsForConnectivity = true
|
|
339
|
+
config.requestCachePolicy = .returnCacheDataElseLoad
|
|
340
|
+
config.httpAdditionalHeaders = [
|
|
381
341
|
"Accept": "application/json",
|
|
382
|
-
"Accept-Language": Locale.preferredLanguages.
|
|
342
|
+
"Accept-Language": Locale.preferredLanguages.prefix(3).joined(separator: ", "),
|
|
383
343
|
]
|
|
384
|
-
|
|
385
|
-
let session = URLSession(configuration: configuration)
|
|
344
|
+
let session = URLSession(configuration: config)
|
|
386
345
|
```
|
|
387
346
|
|
|
388
|
-
`waitsForConnectivity
|
|
389
|
-
|
|
390
|
-
`urlSession(_:taskIsWaitingForConnectivity:)` delegate
|
|
391
|
-
feedback.
|
|
347
|
+
With `waitsForConnectivity`, a request made while offline waits for a usable
|
|
348
|
+
path instead of failing immediately. Implement
|
|
349
|
+
`urlSession(_:taskIsWaitingForConnectivity:)` on the delegate to tell the user.
|
|
392
350
|
|
|
393
351
|
## App Transport Security (ATS)
|
|
394
352
|
|
|
395
|
-
ATS
|
|
396
|
-
ATS is URL Loading System
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
for
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
-
|
|
405
|
-
|
|
406
|
-
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
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
|
-
**DON'T:** Ignore HTTP status codes and decode blindly.
|
|
451
|
-
**DO:** Validate status codes before decoding. A 200 with invalid JSON and
|
|
452
|
-
a 500 with an error body require different handling.
|
|
453
|
-
|
|
454
|
-
## Review Checklist
|
|
455
|
-
|
|
456
|
-
- [ ] Foreground transfers use async/await; background sessions use delegate/task APIs
|
|
457
|
-
- [ ] Error handling covers URLError cases (.notConnectedToInternet, .timedOut, .cancelled)
|
|
458
|
-
- [ ] Requests are cancellable (respect Task cancellation via `.task` modifier or stored Task references)
|
|
459
|
-
- [ ] Authentication tokens injected via middleware, not hardcoded
|
|
460
|
-
- [ ] Response HTTP status codes validated before decoding
|
|
461
|
-
- [ ] Large downloads use `download(for:)` not `data(for:)`
|
|
462
|
-
- [ ] Network calls happen off `@MainActor` (only UI updates on main)
|
|
463
|
-
- [ ] URLSession configured with appropriate timeouts and caching
|
|
464
|
-
- [ ] Production clients inject configured sessions instead of using `URLSession.shared`
|
|
465
|
-
- [ ] Background transfers use task/delegate APIs, not async convenience APIs
|
|
466
|
-
- [ ] Retry logic excludes cancellation and 4xx client errors
|
|
467
|
-
- [ ] Pagination checks `Task.isCancelled` between pages
|
|
468
|
-
- [ ] Sensitive tokens stored in Keychain (not UserDefaults or plain files)
|
|
469
|
-
- [ ] No force-unwrapped URLs from dynamic input
|
|
470
|
-
- [ ] Server error responses decoded and surfaced to users
|
|
471
|
-
- [ ] Network.framework code configures TLS/trust explicitly and keeps deep pinning work in `swift-security`
|
|
472
|
-
- [ ] `NetworkConnection<QUIC>` stream APIs are treated as async throwing
|
|
473
|
-
- [ ] Ensure network response model types conform to Sendable; use @MainActor for UI-updating completion paths
|
|
353
|
+
- ATS requires HTTPS by default. Leave it on.
|
|
354
|
+
- ATS is policy of the URL Loading System. It governs URLSession, not
|
|
355
|
+
Network.framework connections; with Network.framework you configure TLS
|
|
356
|
+
and trust evaluation yourself.
|
|
357
|
+
- A per-domain exception is the last resort, only for a third-party host
|
|
358
|
+
that cannot move to HTTPS. Exceptions need a written justification and
|
|
359
|
+
can draw extra App Review scrutiny.
|
|
360
|
+
- `NSAllowsArbitraryLoads = true` does not belong in a production build
|
|
361
|
+
unless nothing narrower exists.
|
|
362
|
+
- `NSAllowsLocalNetworking` is fine for talking to local devices (Bonjour,
|
|
363
|
+
IoT accessories).
|
|
364
|
+
- Prefer declarative pinning with `NSPinnedDomains`. Hashing the raw bytes
|
|
365
|
+
from `SecKeyCopyExternalRepresentation` is not SPKI pinning; a correct pin
|
|
366
|
+
hashes the Subject Public Key Info. Trust code belongs to `swift-security`.
|
|
367
|
+
|
|
368
|
+
## Common mistakes
|
|
369
|
+
|
|
370
|
+
1. `URLSession.shared` where configuration matters. Build a session with
|
|
371
|
+
timeouts, caching and a delegate.
|
|
372
|
+
2. Force-unwrapping `URL(string:)`. Handle `nil`, whether the string is
|
|
373
|
+
dynamic or a literal; a thrown `invalidURL` costs one line.
|
|
374
|
+
3. Decoding big payloads on the main actor. The awaited call and the decode
|
|
375
|
+
stay off the main actor; hop to `@MainActor` only to publish UI state.
|
|
376
|
+
4. Ignoring cancellation in paging, streaming and retry loops. Test
|
|
377
|
+
`Task.isCancelled` or use `try Task.checkCancellation()`, and start work
|
|
378
|
+
from SwiftUI `.task` so it is cancelled with the view.
|
|
379
|
+
5. Adding Alamofire or Moya for what async URLSession already does. Keep
|
|
380
|
+
libraries for features the SDK lacks, such as image caching.
|
|
381
|
+
6. Mocking URLSession in tests. Stub the transport with a `URLProtocol`
|
|
382
|
+
subclass or swap the client protocol for a double.
|
|
383
|
+
7. `data(for:)` for large files. `download(for:)` avoids the memory spike.
|
|
384
|
+
8. Starting requests in a view's `body` or `init`. Use `.task` or
|
|
385
|
+
`.task(id:)`.
|
|
386
|
+
9. Pasting tokens into individual requests. Middleware centralises them and
|
|
387
|
+
makes refresh possible.
|
|
388
|
+
10. Decoding before checking the status. A 200 with malformed JSON and a 500
|
|
389
|
+
with an error body need different handling.
|
|
390
|
+
|
|
391
|
+
## Review checklist
|
|
392
|
+
|
|
393
|
+
- [ ] Foreground work uses async/await; background sessions use tasks and delegates, not async conveniences
|
|
394
|
+
- [ ] `.notConnectedToInternet`, `.timedOut` and `.cancelled` are handled
|
|
395
|
+
- [ ] Requests are cancellable through `.task` or a stored `Task`
|
|
396
|
+
- [ ] Auth headers come from middleware; tokens live in the Keychain, never UserDefaults or plain files
|
|
397
|
+
- [ ] Status code checked before decoding; server error bodies decoded and shown
|
|
398
|
+
- [ ] Large downloads use `download(for:)`
|
|
399
|
+
- [ ] Networking and decoding run off `@MainActor`; only UI updates hop to main
|
|
400
|
+
- [ ] Session has deliberate timeouts and caching and is injected, not `URLSession.shared`
|
|
401
|
+
- [ ] Retry skips cancellation and 4xx other than 429
|
|
402
|
+
- [ ] Paginators check cancellation between pages
|
|
403
|
+
- [ ] No force-unwrapped URLs or casts
|
|
404
|
+
- [ ] Network.framework code sets TLS and trust explicitly; pinning detail deferred to `swift-security`
|
|
405
|
+
- [ ] `NetworkConnection<QUIC>` stream calls are awaited and can throw
|
|
406
|
+
- [ ] Response models are `Sendable`; code that updates UI is `@MainActor`
|
|
474
407
|
|
|
475
408
|
## References
|
|
476
409
|
|
|
477
|
-
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
-
|
|
482
|
-
configuration, background downloads/uploads, WebSocket patterns with
|
|
483
|
-
structured concurrency, and reconnection strategies.
|
|
484
|
-
- See [references/lightweight-clients.md](references/lightweight-clients.md) for the lightweight closure-based
|
|
485
|
-
client pattern (struct of async closures, injected via init for testability
|
|
486
|
-
and preview support).
|
|
487
|
-
- See [references/network-framework.md](references/network-framework.md) for Network.framework (NWConnection,
|
|
488
|
-
NWListener, NWBrowser, NWPathMonitor) and low-level TCP/UDP/WebSocket patterns.
|
|
489
|
-
- See [references/file-storage-patterns.md](references/file-storage-patterns.md) for file system directory
|
|
490
|
-
selection, FileProtectionType, backup exclusion, and storage pressure handling.
|
|
410
|
+
- [URLSession patterns](references/urlsession-patterns.md): full API client, request builder, multipart upload, download progress, cursor and offset pagination, URLProtocol mocking, retry with jitter, pinning guidance, request logging, caching, SSE, session factory.
|
|
411
|
+
- [background and WebSocket guide](references/background-websocket.md): background session setup, background downloads and uploads, app delegate event handling, WebSocket with structured concurrency, reconnection, typed messages, auth.
|
|
412
|
+
- [lightweight clients](references/lightweight-clients.md): struct-of-closures client injected through `init` for tests and previews.
|
|
413
|
+
- [Network framework guide](references/network-framework.md): `NWConnection`, `NWListener`, `NWBrowser`, `NWPathMonitor`, raw TCP, UDP and WebSocket, iOS 26 `NetworkConnection`.
|
|
414
|
+
- [file storage patterns](references/file-storage-patterns.md): directory choice, `FileProtectionType`, backup exclusion, low-storage handling.
|