@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,500 +1,390 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: background-processing
|
|
3
|
-
description: "
|
|
3
|
+
description: "iOS background work with BGTaskScheduler: BGAppRefreshTask short fetches, BGProcessingTask long maintenance, BGContinuedProcessingTask (iOS 26+) for user-started work, background URLSession transfers, silent pushes, Info.plist identifiers and modes, handler registration, expiration and completion, simulated launches for debugging. Use when adding, reviewing or debugging background work. Not for visible notification UI."
|
|
4
4
|
metadata:
|
|
5
|
-
source:
|
|
5
|
+
source: multi-agent-pipeline
|
|
6
6
|
---
|
|
7
|
+
|
|
7
8
|
# Background Processing
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
iOS gives an app background time only on its own terms. You describe the work,
|
|
11
|
+
the system chooses the moment, or skips it entirely, and your code has to finish
|
|
12
|
+
or stop cleanly whenever it is told to. This skill covers the BackgroundTasks
|
|
13
|
+
framework, background `URLSession` transfers and silent pushes.
|
|
11
14
|
|
|
12
|
-
|
|
15
|
+
Covers: background `URLSession` downloads and uploads. Not for designing
|
|
16
|
+
visible notification UI.
|
|
13
17
|
|
|
14
|
-
|
|
15
|
-
- [BGTaskScheduler Registration](#bgtaskscheduler-registration)
|
|
16
|
-
- [BGAppRefreshTask Patterns](#bgapprefreshtask-patterns)
|
|
17
|
-
- [BGProcessingTask Patterns](#bgprocessingtask-patterns)
|
|
18
|
-
- [BGContinuedProcessingTask (iOS 26+)](#bgcontinuedprocessingtask-ios-26)
|
|
19
|
-
- [Background URLSession Downloads](#background-urlsession-downloads)
|
|
20
|
-
- [Background Push Triggers](#background-push-triggers)
|
|
21
|
-
- [Common Mistakes](#common-mistakes)
|
|
22
|
-
- [Review Checklist](#review-checklist)
|
|
23
|
-
- [References](#references)
|
|
18
|
+
## Configuration
|
|
24
19
|
|
|
25
|
-
|
|
20
|
+
**Permitted identifiers.** Every task identifier goes into the Info.plist
|
|
21
|
+
array `BGTaskSchedulerPermittedIdentifiers`. A missing entry makes
|
|
22
|
+
`submit(_:)` throw `BGTaskScheduler.Error.Code.notPermitted`. An entry may end
|
|
23
|
+
in a wildcard, such as `org.sample.photos.export.*`, for per-job identifiers.
|
|
26
24
|
|
|
27
|
-
|
|
28
|
-
`
|
|
29
|
-
|
|
25
|
+
**Background modes.** `UIBackgroundModes` needs `fetch` for app refresh tasks
|
|
26
|
+
and `processing` for processing tasks. In Xcode: target, Signing &
|
|
27
|
+
Capabilities, Background Modes, then tick "Background fetch" and "Background
|
|
28
|
+
processing".
|
|
30
29
|
|
|
31
30
|
```xml
|
|
32
31
|
<key>BGTaskSchedulerPermittedIdentifiers</key>
|
|
33
32
|
<array>
|
|
34
|
-
<string>
|
|
35
|
-
<string>
|
|
36
|
-
<string>
|
|
33
|
+
<string>org.sample.photos.refresh</string>
|
|
34
|
+
<string>org.sample.photos.cleanup</string>
|
|
35
|
+
<string>org.sample.photos.export.*</string>
|
|
37
36
|
</array>
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
Also enable the required `UIBackgroundModes`:
|
|
41
|
-
|
|
42
|
-
```xml
|
|
43
37
|
<key>UIBackgroundModes</key>
|
|
44
38
|
<array>
|
|
45
|
-
<string>fetch</string>
|
|
46
|
-
<string>processing</string>
|
|
39
|
+
<string>fetch</string>
|
|
40
|
+
<string>processing</string>
|
|
47
41
|
</array>
|
|
48
42
|
```
|
|
49
43
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
## BGTaskScheduler Registration
|
|
44
|
+
## Registering handlers
|
|
53
45
|
|
|
54
|
-
|
|
55
|
-
`application(_:didFinishLaunchingWithOptions:)
|
|
56
|
-
|
|
57
|
-
|
|
46
|
+
Refresh and processing handlers must be registered before the app finishes
|
|
47
|
+
launching: in `application(_:didFinishLaunchingWithOptions:)` for UIKit, or in
|
|
48
|
+
the `App` initializer for SwiftUI. Registering later, for instance from a
|
|
49
|
+
view's `.task`, is too late for the system to deliver a launch. Continued
|
|
50
|
+
processing tasks are the one exception, covered below. Register each
|
|
51
|
+
identifier once: a second registration of the same identifier kills the app.
|
|
58
52
|
|
|
59
53
|
```swift
|
|
60
54
|
import BackgroundTasks
|
|
55
|
+
import SwiftUI
|
|
61
56
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
_ application: UIApplication,
|
|
66
|
-
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
|
67
|
-
) -> Bool {
|
|
68
|
-
BGTaskScheduler.shared.register(
|
|
69
|
-
forTaskWithIdentifier: "com.example.app.refresh",
|
|
70
|
-
using: nil // nil = default background queue
|
|
71
|
-
) { task in
|
|
72
|
-
self.handleAppRefresh(task: task as! BGAppRefreshTask)
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
BGTaskScheduler.shared.register(
|
|
76
|
-
forTaskWithIdentifier: "com.example.app.db-cleanup",
|
|
77
|
-
using: nil
|
|
78
|
-
) { task in
|
|
79
|
-
self.handleDatabaseCleanup(task: task as! BGProcessingTask)
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
return true
|
|
83
|
-
}
|
|
57
|
+
enum TaskID {
|
|
58
|
+
static let refresh = "org.sample.photos.refresh"
|
|
59
|
+
static let cleanup = "org.sample.photos.cleanup"
|
|
84
60
|
}
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
### SwiftUI Registration
|
|
88
|
-
|
|
89
|
-
```swift
|
|
90
|
-
import SwiftUI
|
|
91
|
-
import BackgroundTasks
|
|
92
61
|
|
|
93
62
|
@main
|
|
94
|
-
struct
|
|
63
|
+
struct PhotosApp: App {
|
|
95
64
|
init() {
|
|
96
|
-
BGTaskScheduler.shared.register(
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
)
|
|
65
|
+
BGTaskScheduler.shared.register(forTaskWithIdentifier: TaskID.refresh, using: nil) { task in
|
|
66
|
+
guard let refresh = task as? BGAppRefreshTask else { return }
|
|
67
|
+
LibrarySync.shared.handleRefresh(refresh)
|
|
68
|
+
}
|
|
69
|
+
BGTaskScheduler.shared.register(forTaskWithIdentifier: TaskID.cleanup, using: nil) { task in
|
|
70
|
+
guard let cleanup = task as? BGProcessingTask else { return }
|
|
71
|
+
LibrarySync.shared.handleCleanup(cleanup)
|
|
103
72
|
}
|
|
104
73
|
}
|
|
105
74
|
|
|
106
75
|
var body: some Scene {
|
|
107
|
-
WindowGroup {
|
|
76
|
+
WindowGroup {
|
|
77
|
+
LibraryView()
|
|
78
|
+
}
|
|
108
79
|
}
|
|
109
80
|
}
|
|
110
81
|
```
|
|
111
82
|
|
|
112
|
-
|
|
83
|
+
Passing `nil` for `using:` runs the handler on a default background queue.
|
|
84
|
+
|
|
85
|
+
## App refresh tasks
|
|
113
86
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
87
|
+
A refresh task gets roughly 30 seconds for a small update. The system picks
|
|
88
|
+
the moment based on how the user uses the app. `earliestBeginDate` only says
|
|
89
|
+
"not before"; the task may run much later or not at all, and reviews should
|
|
90
|
+
treat it that way.
|
|
117
91
|
|
|
118
92
|
```swift
|
|
119
|
-
|
|
120
|
-
let
|
|
121
|
-
|
|
122
|
-
)
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
print("Could not schedule app refresh: \(error)")
|
|
93
|
+
final class LibrarySync: Sendable {
|
|
94
|
+
static let shared = LibrarySync()
|
|
95
|
+
|
|
96
|
+
func scheduleRefresh() {
|
|
97
|
+
let request = BGAppRefreshTaskRequest(identifier: TaskID.refresh)
|
|
98
|
+
request.earliestBeginDate = .now.addingTimeInterval(15 * 60)
|
|
99
|
+
if (try? BGTaskScheduler.shared.submit(request)) == nil {
|
|
100
|
+
Log.background.error("Refresh request was rejected")
|
|
101
|
+
}
|
|
129
102
|
}
|
|
130
|
-
}
|
|
131
103
|
|
|
132
|
-
func
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
let fetchTask = Task {
|
|
137
|
-
do {
|
|
138
|
-
let data = try await APIClient.shared.fetchLatestFeed()
|
|
139
|
-
await FeedStore.shared.update(with: data)
|
|
140
|
-
task.setTaskCompleted(success: true)
|
|
141
|
-
} catch {
|
|
142
|
-
task.setTaskCompleted(success: false)
|
|
143
|
-
}
|
|
104
|
+
func handleRefresh(_ task: BGAppRefreshTask) {
|
|
105
|
+
scheduleRefresh()
|
|
106
|
+
perform(task) { try await self.fetchNewAlbums() }
|
|
144
107
|
}
|
|
108
|
+
}
|
|
145
109
|
|
|
146
|
-
|
|
147
|
-
task
|
|
148
|
-
|
|
149
|
-
|
|
110
|
+
func perform(_ task: BGTask, _ job: @escaping @Sendable () async throws -> Void) {
|
|
111
|
+
nonisolated(unsafe) let task = task
|
|
112
|
+
let work = Task {
|
|
113
|
+
let finished = (try? await job()) != nil
|
|
114
|
+
task.setTaskCompleted(success: finished && !Task.isCancelled)
|
|
150
115
|
}
|
|
116
|
+
task.expirationHandler = { work.cancel() }
|
|
151
117
|
}
|
|
152
118
|
```
|
|
153
119
|
|
|
154
|
-
|
|
120
|
+
`Log.background` stands for an `os.Logger` owned by the app, and the
|
|
121
|
+
processing example below reuses `perform`. Two habits matter: schedule the
|
|
122
|
+
next run first, so a crash or expiry does not break the chain, and always
|
|
123
|
+
install an `expirationHandler`, because the system can take the time back at
|
|
124
|
+
any moment. The handler only cancels; the work `Task` is the one completion
|
|
125
|
+
path, so `setTaskCompleted(success:)` runs exactly once, with `false` after an
|
|
126
|
+
expiry. That relies on the job honouring cancellation (`URLSession` async calls
|
|
127
|
+
and `Task.checkCancellation()` throw), so the `Task` ends soon after the
|
|
128
|
+
handler fires.
|
|
155
129
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
`
|
|
130
|
+
`BGTask` is not `Sendable`, yet both the work `Task` and the expiration
|
|
131
|
+
handler need it, and the handler runs on a queue the framework picks. The
|
|
132
|
+
`nonisolated(unsafe)` local copy marks that sharing as deliberate; without it
|
|
133
|
+
Swift 6 rejects the `Task` closure with "passing closure as a 'sending'
|
|
134
|
+
parameter risks causing data races".
|
|
135
|
+
|
|
136
|
+
## Processing tasks
|
|
137
|
+
|
|
138
|
+
Processing tasks can run for minutes and suit maintenance, indexing and
|
|
139
|
+
cleanup. They start only while the device is idle, and optionally only on
|
|
140
|
+
power.
|
|
159
141
|
|
|
160
142
|
```swift
|
|
161
|
-
func
|
|
162
|
-
let request = BGProcessingTaskRequest(
|
|
163
|
-
identifier: "com.example.app.db-cleanup"
|
|
164
|
-
)
|
|
143
|
+
func scheduleCleanup() {
|
|
144
|
+
let request = BGProcessingTaskRequest(identifier: TaskID.cleanup)
|
|
165
145
|
request.requiresNetworkConnectivity = false
|
|
166
146
|
request.requiresExternalPower = true
|
|
167
|
-
request.earliestBeginDate =
|
|
168
|
-
|
|
169
|
-
do {
|
|
170
|
-
try BGTaskScheduler.shared.submit(request)
|
|
171
|
-
} catch {
|
|
172
|
-
print("Could not schedule processing task: \(error)")
|
|
173
|
-
}
|
|
147
|
+
request.earliestBeginDate = .now.addingTimeInterval(3600)
|
|
148
|
+
try? BGTaskScheduler.shared.submit(request)
|
|
174
149
|
}
|
|
175
150
|
|
|
176
|
-
func
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
let cleanupTask = Task {
|
|
180
|
-
do {
|
|
181
|
-
try await DatabaseManager.shared.purgeExpiredRecords()
|
|
182
|
-
try await DatabaseManager.shared.rebuildIndexes()
|
|
183
|
-
task.setTaskCompleted(success: true)
|
|
184
|
-
} catch {
|
|
185
|
-
task.setTaskCompleted(success: false)
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
task.expirationHandler = {
|
|
190
|
-
cleanupTask.cancel()
|
|
191
|
-
task.setTaskCompleted(success: false)
|
|
192
|
-
}
|
|
151
|
+
func handleCleanup(_ task: BGProcessingTask) {
|
|
152
|
+
scheduleCleanup()
|
|
153
|
+
perform(task) { try await purgeOrphanedThumbnails() }
|
|
193
154
|
}
|
|
194
155
|
```
|
|
195
156
|
|
|
196
|
-
##
|
|
157
|
+
## Continued processing tasks (iOS 26+)
|
|
197
158
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
159
|
+
`BGContinuedProcessingTask` needs iOS or iPadOS 26.0 and is for work the user
|
|
160
|
+
starts on purpose, such as an export, that should keep going after they leave
|
|
161
|
+
the app. It differs from the other two types:
|
|
201
162
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
163
|
+
- It starts right away from the foreground instead of waiting for the system.
|
|
164
|
+
- The system shows its progress to the user as a Live Activity; the task
|
|
165
|
+
adopts `ProgressReporting`.
|
|
166
|
+
- Under resource pressure the system may end it, and tasks reporting little
|
|
167
|
+
progress go first.
|
|
168
|
+
- The user or the system can cancel it; the `expirationHandler` must stop the
|
|
169
|
+
work and remove partial output, and the cancelled work then completes once.
|
|
170
|
+
- Its handler is exempt from the register-at-launch rule. `submit` expects
|
|
171
|
+
an identifier that already has a handler, so register the job's identifier
|
|
172
|
+
right before submitting it.
|
|
207
173
|
|
|
208
174
|
```swift
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
175
|
+
func startExport(of album: Album) {
|
|
176
|
+
let identifier = "org.sample.photos.export.\(UUID().uuidString)"
|
|
177
|
+
BGTaskScheduler.shared.register(forTaskWithIdentifier: identifier, using: nil) { task in
|
|
178
|
+
guard let export = task as? BGContinuedProcessingTask else { return }
|
|
179
|
+
handleExport(export)
|
|
180
|
+
}
|
|
215
181
|
let request = BGContinuedProcessingTaskRequest(
|
|
216
|
-
identifier:
|
|
217
|
-
title: "Exporting
|
|
218
|
-
subtitle: "
|
|
182
|
+
identifier: identifier,
|
|
183
|
+
title: "Exporting \(album.name)",
|
|
184
|
+
subtitle: "Preparing"
|
|
219
185
|
)
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
request
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
try BGTaskScheduler.shared.submit(request)
|
|
228
|
-
} catch {
|
|
229
|
-
print("Could not submit continued processing task: \(error)")
|
|
186
|
+
request.strategy = .fail
|
|
187
|
+
if BGTaskScheduler.supportedResources.contains(.gpu) {
|
|
188
|
+
request.requiredResources = .gpu
|
|
189
|
+
}
|
|
190
|
+
guard (try? BGTaskScheduler.shared.submit(request)) != nil else {
|
|
191
|
+
showExportUnavailable()
|
|
192
|
+
return
|
|
230
193
|
}
|
|
231
194
|
}
|
|
232
195
|
|
|
233
|
-
func
|
|
234
|
-
let
|
|
235
|
-
let
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
if Task.isCancelled { break }
|
|
240
|
-
|
|
241
|
-
await PhotoExporter.shared.export(item)
|
|
242
|
-
progress.completedUnitCount = Int64(index + 1)
|
|
243
|
-
|
|
244
|
-
// Update the user-facing title/subtitle
|
|
245
|
-
task.updateTitle(
|
|
246
|
-
"Exporting Photos",
|
|
247
|
-
subtitle: "\(index + 1) of \(items.count) complete"
|
|
248
|
-
)
|
|
196
|
+
func handleExport(_ task: BGContinuedProcessingTask) {
|
|
197
|
+
nonisolated(unsafe) let task = task
|
|
198
|
+
let work = Task { await runExport(task, items: ExportQueue.shared.pending) }
|
|
199
|
+
task.expirationHandler = {
|
|
200
|
+
work.cancel()
|
|
201
|
+
ExportQueue.shared.removePartialFiles()
|
|
249
202
|
}
|
|
203
|
+
}
|
|
250
204
|
|
|
205
|
+
func runExport(_ task: BGContinuedProcessingTask, items: [PhotoAsset]) async {
|
|
206
|
+
task.progress.totalUnitCount = Int64(items.count)
|
|
207
|
+
var done: Int64 = 0
|
|
208
|
+
for asset in items where !Task.isCancelled {
|
|
209
|
+
await render(asset)
|
|
210
|
+
done += 1
|
|
211
|
+
task.progress.completedUnitCount = done
|
|
212
|
+
task.updateTitle(task.title, subtitle: "Photo \(done) of \(items.count)")
|
|
213
|
+
}
|
|
251
214
|
task.setTaskCompleted(success: !Task.isCancelled)
|
|
252
215
|
}
|
|
253
216
|
```
|
|
254
217
|
|
|
255
|
-
|
|
218
|
+
When the task expires, the cancelled `Task` leaves the loop and
|
|
219
|
+
`runExport` reports `success: false`, so completion still happens exactly once. The
|
|
220
|
+
`nonisolated(unsafe)` copy in `handleExport` is there for the same reason as in
|
|
221
|
+
`perform`.
|
|
256
222
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
223
|
+
- Each job gets its own identifier under a permitted wildcard base, which
|
|
224
|
+
also keeps every registration unique.
|
|
225
|
+
- `earliestBeginDate` is ignored for this request type.
|
|
226
|
+
- `strategy` is `.queue` (start as soon as allowed) or `.fail` (reject the
|
|
227
|
+
submission if it cannot start now).
|
|
228
|
+
- GPU work needs both the `supportedResources` check and the entitlement for
|
|
229
|
+
background GPU use (key ending in `continued-processing.gpu`).
|
|
263
230
|
|
|
264
|
-
|
|
231
|
+
Strategy choice, cancellation and progress ranking are expanded in the
|
|
232
|
+
[patterns reference](references/background-task-patterns.md).
|
|
265
233
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
234
|
+
## Background URLSession
|
|
235
|
+
|
|
236
|
+
A session built with `URLSessionConfiguration.background(withIdentifier:)`
|
|
237
|
+
hands transfers to a system daemon, so they keep running while the app is
|
|
238
|
+
suspended or even terminated.
|
|
269
239
|
|
|
270
240
|
```swift
|
|
271
|
-
|
|
272
|
-
|
|
241
|
+
@MainActor
|
|
242
|
+
final class EpisodeDownloader: NSObject, URLSessionDownloadDelegate {
|
|
243
|
+
static let shared = EpisodeDownloader()
|
|
244
|
+
|
|
245
|
+
var finishEvents: (() -> Void)?
|
|
273
246
|
|
|
274
|
-
|
|
247
|
+
lazy var session: URLSession = {
|
|
275
248
|
let config = URLSessionConfiguration.background(
|
|
276
|
-
withIdentifier: "
|
|
249
|
+
withIdentifier: "org.sample.podcasts.downloads"
|
|
277
250
|
)
|
|
278
|
-
config.isDiscretionary = true
|
|
279
251
|
config.sessionSendsLaunchEvents = true
|
|
280
|
-
|
|
252
|
+
config.isDiscretionary = true
|
|
253
|
+
return URLSession(
|
|
254
|
+
configuration: config,
|
|
255
|
+
delegate: self,
|
|
256
|
+
delegateQueue: nil
|
|
257
|
+
)
|
|
281
258
|
}()
|
|
282
259
|
|
|
283
|
-
func
|
|
284
|
-
let
|
|
285
|
-
|
|
286
|
-
|
|
260
|
+
func fetch(_ remote: URL, notBefore date: Date? = nil) {
|
|
261
|
+
let job = session.downloadTask(with: remote)
|
|
262
|
+
job.earliestBeginDate = date
|
|
263
|
+
job.resume()
|
|
287
264
|
}
|
|
288
265
|
|
|
289
|
-
func urlSession(
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
) {
|
|
294
|
-
// Move file from tmp before this method returns
|
|
295
|
-
let dest = FileManager.default.urls(
|
|
296
|
-
for: .documentDirectory, in: .userDomainMask
|
|
297
|
-
)[0].appendingPathComponent("download.dat")
|
|
298
|
-
try? FileManager.default.moveItem(at: location, to: dest)
|
|
266
|
+
nonisolated func urlSession(_ s: URLSession, downloadTask job: URLSessionDownloadTask, didFinishDownloadingTo tmp: URL) {
|
|
267
|
+
let name = job.response?.suggestedFilename ?? "episode-\(job.taskIdentifier)"
|
|
268
|
+
let target = URL.documentsDirectory.appending(path: name)
|
|
269
|
+
try? FileManager.default.moveItem(at: tmp, to: target)
|
|
299
270
|
}
|
|
300
271
|
|
|
301
|
-
func urlSession(
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
didCompleteWithError error: (any Error)?
|
|
305
|
-
) {
|
|
306
|
-
if let error { print("Download failed: \(error)") }
|
|
272
|
+
nonisolated func urlSession(_ s: URLSession, task job: URLSessionTask, didCompleteWithError error: (any Error)?) {
|
|
273
|
+
guard let error else { return }
|
|
274
|
+
Log.background.error("Transfer failed: \(error)")
|
|
307
275
|
}
|
|
308
|
-
}
|
|
309
|
-
```
|
|
310
|
-
|
|
311
|
-
Handle app relaunch - store and invoke the system completion handler:
|
|
312
|
-
|
|
313
|
-
```swift
|
|
314
|
-
// In AppDelegate:
|
|
315
|
-
func application(
|
|
316
|
-
_ application: UIApplication,
|
|
317
|
-
handleEventsForBackgroundURLSession identifier: String,
|
|
318
|
-
completionHandler: @escaping () -> Void
|
|
319
|
-
) {
|
|
320
|
-
backgroundSessionCompletionHandler = completionHandler
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
// In URLSessionDelegate - call stored handler when events finish:
|
|
324
|
-
func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
|
|
325
|
-
Task { @MainActor in
|
|
326
|
-
self.backgroundSessionCompletionHandler?()
|
|
327
|
-
self.backgroundSessionCompletionHandler = nil
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
```
|
|
331
|
-
|
|
332
|
-
## Background Push Triggers
|
|
333
|
-
|
|
334
|
-
Silent push notifications wake your app briefly to fetch new content. Set
|
|
335
|
-
`content-available: 1` in the push payload.
|
|
336
|
-
|
|
337
|
-
```json
|
|
338
|
-
{ "aps": { "content-available": 1 }, "custom-data": "new-messages" }
|
|
339
|
-
```
|
|
340
|
-
|
|
341
|
-
Send the APNs request with `apns-push-type: background` and
|
|
342
|
-
`apns-priority: 5`. Background push delivery is low priority and not
|
|
343
|
-
guaranteed; keep sends infrequent, generally no more than two or three per
|
|
344
|
-
hour.
|
|
345
|
-
|
|
346
|
-
Handle in AppDelegate:
|
|
347
276
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
fetchCompletionHandler completionHandler:
|
|
353
|
-
@escaping (UIBackgroundFetchResult) -> Void
|
|
354
|
-
) {
|
|
355
|
-
Task {
|
|
356
|
-
do {
|
|
357
|
-
let hasNew = try await MessageStore.shared.fetchNewMessages()
|
|
358
|
-
completionHandler(hasNew ? .newData : .noData)
|
|
359
|
-
} catch {
|
|
360
|
-
completionHandler(.failed)
|
|
277
|
+
nonisolated func urlSessionDidFinishEvents(forBackgroundURLSession s: URLSession) {
|
|
278
|
+
Task { @MainActor in
|
|
279
|
+
self.finishEvents?()
|
|
280
|
+
self.finishEvents = nil
|
|
361
281
|
}
|
|
362
282
|
}
|
|
363
283
|
}
|
|
364
284
|
```
|
|
365
285
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
```swift
|
|
377
|
-
// DON'T: Submit a task whose identifier isn't in BGTaskSchedulerPermittedIdentifiers
|
|
378
|
-
let request = BGAppRefreshTaskRequest(identifier: "com.example.app.refresh")
|
|
379
|
-
try BGTaskScheduler.shared.submit(request) // Throws .notPermitted
|
|
380
|
-
|
|
381
|
-
// DO: Add every identifier to Info.plist BGTaskSchedulerPermittedIdentifiers
|
|
382
|
-
// <string>com.example.app.refresh</string>
|
|
383
|
-
```
|
|
286
|
+
- The class is `@MainActor`, so the lazy session and the stored handler are
|
|
287
|
+
safe to touch from UIKit callbacks. The session calls its delegate on its
|
|
288
|
+
own queue, hence the `nonisolated` delegate methods.
|
|
289
|
+
- `isDiscretionary` lets the system wait for good conditions. With
|
|
290
|
+
`sessionSendsLaunchEvents` on, finished transfers launch the app again.
|
|
291
|
+
- A delegate is required (`delegateQueue: nil` is fine). The async and
|
|
292
|
+
completion-handler task APIs do not work with background sessions.
|
|
293
|
+
- The temporary file disappears when `didFinishDownloadingTo` returns, so move
|
|
294
|
+
it inside that method.
|
|
295
|
+
- Failures arrive in `urlSession(_:task:didCompleteWithError:)`.
|
|
384
296
|
|
|
385
|
-
|
|
297
|
+
When the system relaunches the app for session events, keep the handler it
|
|
298
|
+
gives you and call it once the delegate reports the events are done:
|
|
386
299
|
|
|
387
300
|
```swift
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
// Missing: task.setTaskCompleted(success:)
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
// DO: Always call setTaskCompleted on every code path
|
|
398
|
-
func handleRefresh(task: BGAppRefreshTask) {
|
|
399
|
-
let work = Task {
|
|
400
|
-
do {
|
|
401
|
-
let data = try await fetchData()
|
|
402
|
-
await store.update(data)
|
|
403
|
-
task.setTaskCompleted(success: true)
|
|
404
|
-
} catch {
|
|
405
|
-
task.setTaskCompleted(success: false)
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
task.expirationHandler = {
|
|
409
|
-
work.cancel()
|
|
410
|
-
task.setTaskCompleted(success: false)
|
|
411
|
-
}
|
|
301
|
+
func application(_ app: UIApplication,
|
|
302
|
+
handleEventsForBackgroundURLSession id: String,
|
|
303
|
+
completionHandler done: @escaping () -> Void) {
|
|
304
|
+
EpisodeDownloader.shared.finishEvents = done
|
|
305
|
+
_ = EpisodeDownloader.shared.session
|
|
412
306
|
}
|
|
413
307
|
```
|
|
414
308
|
|
|
415
|
-
|
|
309
|
+
## Background pushes
|
|
416
310
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
Task { await heavyWork() }
|
|
421
|
-
// No expirationHandler -- system terminates ungracefully
|
|
422
|
-
}
|
|
311
|
+
A silent push wakes the app for a short time. The payload carries
|
|
312
|
+
`content-available: 1` plus your own keys. On the APNs request, set the
|
|
313
|
+
push type header to `background` and the priority header to `5`.
|
|
423
314
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
work.cancel()
|
|
429
|
-
task.setTaskCompleted(success: false)
|
|
430
|
-
}
|
|
315
|
+
```json
|
|
316
|
+
{
|
|
317
|
+
"aps": { "content-available": 1 },
|
|
318
|
+
"album-id": "A41"
|
|
431
319
|
}
|
|
432
320
|
```
|
|
433
321
|
|
|
434
|
-
|
|
322
|
+
Delivery is low priority and never guaranteed. Keep it to two or three per hour
|
|
323
|
+
at most. Enable Remote notifications under Background Modes and call
|
|
324
|
+
`UIApplication.shared.registerForRemoteNotifications()`.
|
|
435
325
|
|
|
436
326
|
```swift
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
// earliestBeginDate is a hint -- the system chooses actual launch time
|
|
443
|
-
```
|
|
444
|
-
|
|
445
|
-
### 5. Over-relying on background time
|
|
446
|
-
|
|
447
|
-
```swift
|
|
448
|
-
// DON'T: Start a 10-minute operation assuming it will finish
|
|
449
|
-
func handleRefresh(task: BGAppRefreshTask) {
|
|
450
|
-
Task { await tenMinuteSync() }
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
// DO: Design work to be incremental and cancellable
|
|
454
|
-
func handleRefresh(task: BGAppRefreshTask) {
|
|
455
|
-
let work = Task {
|
|
456
|
-
for batch in batches {
|
|
457
|
-
try Task.checkCancellation()
|
|
458
|
-
await processBatch(batch)
|
|
459
|
-
await saveBatchProgress(batch)
|
|
460
|
-
}
|
|
461
|
-
task.setTaskCompleted(success: true)
|
|
327
|
+
func application(_ app: UIApplication,
|
|
328
|
+
didReceiveRemoteNotification payload: [AnyHashable: Any],
|
|
329
|
+
fetchCompletionHandler reply: @escaping (UIBackgroundFetchResult) -> Void) {
|
|
330
|
+
guard let albumID = payload["album-id"] as? String else {
|
|
331
|
+
return reply(.noData)
|
|
462
332
|
}
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
333
|
+
Task {
|
|
334
|
+
let outcome: Result<Bool, any Error> = await LibrarySync.shared.refreshAlbum(albumID)
|
|
335
|
+
switch outcome {
|
|
336
|
+
case .success(true): reply(.newData)
|
|
337
|
+
case .success(false): reply(.noData)
|
|
338
|
+
case .failure: reply(.failed)
|
|
339
|
+
}
|
|
466
340
|
}
|
|
467
341
|
}
|
|
468
342
|
```
|
|
469
343
|
|
|
470
|
-
##
|
|
471
|
-
|
|
472
|
-
-
|
|
473
|
-
-
|
|
474
|
-
|
|
475
|
-
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
-
|
|
479
|
-
|
|
480
|
-
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
- [ ]
|
|
487
|
-
- [ ]
|
|
344
|
+
## Common mistakes
|
|
345
|
+
|
|
346
|
+
- **Identifier missing from Info.plist.** `submit` throws `.notPermitted`.
|
|
347
|
+
- **Skipping `setTaskCompleted(success:)`.** The system schedules the app less
|
|
348
|
+
often afterwards. Complete on every path, errors included.
|
|
349
|
+
- **No expiration handler.** The task is killed abruptly instead of stopping
|
|
350
|
+
cleanly. Cancel the work from the handler and let the cancelled work report
|
|
351
|
+
`success: false`, so completion happens once.
|
|
352
|
+
- **Asking too often.** A refresh every minute gets throttled hard. Use 15
|
|
353
|
+
minutes or more, and remember `earliestBeginDate` is a hint.
|
|
354
|
+
- **Too much work per run.** A ten-minute job inside a refresh task will not
|
|
355
|
+
finish. Split work into small cancellable batches, call
|
|
356
|
+
`try Task.checkCancellation()` between them and save progress.
|
|
357
|
+
|
|
358
|
+
## Review checklist
|
|
359
|
+
|
|
360
|
+
- [ ] Every identifier is in `BGTaskSchedulerPermittedIdentifiers`
|
|
361
|
+
- [ ] `UIBackgroundModes` has `fetch` and/or `processing` as needed
|
|
362
|
+
- [ ] Refresh and processing handlers are registered before launch completes;
|
|
363
|
+
continued processing handlers are registered before their `submit`
|
|
364
|
+
- [ ] `setTaskCompleted(success:)` runs exactly once on every path
|
|
365
|
+
- [ ] `expirationHandler` cancels in-flight work
|
|
366
|
+
- [ ] The handler schedules the next task
|
|
367
|
+
- [ ] `earliestBeginDate` is sensible and read as a lower bound only
|
|
368
|
+
- [ ] Background `URLSession` uses a delegate, not async or closure APIs
|
|
369
|
+
- [ ] Downloaded files are moved inside `didFinishDownloadingTo`
|
|
370
|
+
- [ ] `handleEventsForBackgroundURLSession` stores its handler and it is called later
|
|
371
|
+
- [ ] Silent push payload has `content-available: 1`
|
|
372
|
+
- [ ] APNs headers: push type `background`, priority `5`
|
|
373
|
+
- [ ] `fetchCompletionHandler` is called promptly with the right result
|
|
374
|
+
- [ ] Continued processing tasks report progress through `ProgressReporting`
|
|
375
|
+
- [ ] Work is incremental and checks cancellation (`Task.checkCancellation()`)
|
|
376
|
+
- [ ] Handlers never block on synchronous work
|
|
488
377
|
|
|
489
378
|
## References
|
|
490
379
|
|
|
491
|
-
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
380
|
+
- [Background task patterns](references/background-task-patterns.md):
|
|
381
|
+
debugging, error codes, checkpointing, URLSession extras, push limits,
|
|
382
|
+
SwiftUI `.backgroundTask`, continued-processing details
|
|
383
|
+
- Apple documentation:
|
|
384
|
+
[scheduler](https://developer.apple.com/documentation/backgroundtasks/bgtaskscheduler),
|
|
385
|
+
[refresh task](https://developer.apple.com/documentation/backgroundtasks/bgapprefreshtask),
|
|
386
|
+
[processing task](https://developer.apple.com/documentation/backgroundtasks/bgprocessingtask),
|
|
387
|
+
[continued processing task](https://developer.apple.com/documentation/backgroundtasks/bgcontinuedprocessingtask) (iOS 26+),
|
|
388
|
+
[continued processing request](https://developer.apple.com/documentation/backgroundtasks/bgcontinuedprocessingtaskrequest) (iOS 26+),
|
|
389
|
+
[keeping content fresh with background tasks](https://developer.apple.com/documentation/uikit/using-background-tasks-to-update-your-app),
|
|
390
|
+
[long-running work](https://developer.apple.com/documentation/backgroundtasks/performing-long-running-tasks-on-ios-and-ipados)
|