@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,500 +1,388 @@
|
|
|
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.
|
|
113
84
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
85
|
+
## App refresh tasks
|
|
86
|
+
|
|
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
|
-
|
|
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)
|
|
115
|
+
}
|
|
147
116
|
task.expirationHandler = {
|
|
148
|
-
|
|
117
|
+
work.cancel()
|
|
149
118
|
task.setTaskCompleted(success: false)
|
|
150
119
|
}
|
|
151
120
|
}
|
|
152
121
|
```
|
|
153
122
|
|
|
154
|
-
|
|
123
|
+
`Log.background` stands for an `os.Logger` owned by the app, and the
|
|
124
|
+
processing example below reuses `perform`. Two habits matter: schedule the
|
|
125
|
+
next run first, so a crash or expiry does not break the chain, and always
|
|
126
|
+
install an `expirationHandler`, because the system can take the time back at
|
|
127
|
+
any moment.
|
|
155
128
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
`
|
|
129
|
+
`BGTask` is not `Sendable`, yet both the work `Task` and the expiration
|
|
130
|
+
handler need it, and the handler runs on a queue the framework picks. The
|
|
131
|
+
`nonisolated(unsafe)` local copy marks that sharing as deliberate; without it
|
|
132
|
+
Swift 6 rejects the `Task` closure with "passing closure as a 'sending'
|
|
133
|
+
parameter risks causing data races".
|
|
134
|
+
|
|
135
|
+
## Processing tasks
|
|
136
|
+
|
|
137
|
+
Processing tasks can run for minutes and suit maintenance, indexing and
|
|
138
|
+
cleanup. They start only while the device is idle, and optionally only on
|
|
139
|
+
power.
|
|
159
140
|
|
|
160
141
|
```swift
|
|
161
|
-
func
|
|
162
|
-
let request = BGProcessingTaskRequest(
|
|
163
|
-
identifier: "com.example.app.db-cleanup"
|
|
164
|
-
)
|
|
142
|
+
func scheduleCleanup() {
|
|
143
|
+
let request = BGProcessingTaskRequest(identifier: TaskID.cleanup)
|
|
165
144
|
request.requiresNetworkConnectivity = false
|
|
166
145
|
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
|
-
}
|
|
146
|
+
request.earliestBeginDate = .now.addingTimeInterval(3600)
|
|
147
|
+
try? BGTaskScheduler.shared.submit(request)
|
|
174
148
|
}
|
|
175
149
|
|
|
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
|
-
}
|
|
150
|
+
func handleCleanup(_ task: BGProcessingTask) {
|
|
151
|
+
scheduleCleanup()
|
|
152
|
+
perform(task) { try await purgeOrphanedThumbnails() }
|
|
193
153
|
}
|
|
194
154
|
```
|
|
195
155
|
|
|
196
|
-
##
|
|
197
|
-
|
|
198
|
-
A task initiated in the foreground by a user action that continues running in the
|
|
199
|
-
background. The system displays progress via a Live Activity. Conforms to
|
|
200
|
-
`ProgressReporting`.
|
|
156
|
+
## Continued processing tasks (iOS 26+)
|
|
201
157
|
|
|
202
|
-
|
|
158
|
+
`BGContinuedProcessingTask` needs iOS or iPadOS 26.0 and is for work the user
|
|
159
|
+
starts on purpose, such as an export, that should keep going after they leave
|
|
160
|
+
the app. It differs from the other two types:
|
|
203
161
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
162
|
+
- It starts right away from the foreground instead of waiting for the system.
|
|
163
|
+
- The system shows its progress to the user as a Live Activity; the task
|
|
164
|
+
adopts `ProgressReporting`.
|
|
165
|
+
- Under resource pressure the system may end it, and tasks reporting little
|
|
166
|
+
progress go first.
|
|
167
|
+
- The user or the system can cancel it; the `expirationHandler` must stop the
|
|
168
|
+
work and remove partial output before completing.
|
|
169
|
+
- Its handler is exempt from the register-at-launch rule. `submit` expects
|
|
170
|
+
an identifier that already has a handler, so register the job's identifier
|
|
171
|
+
right before submitting it.
|
|
207
172
|
|
|
208
173
|
```swift
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
174
|
+
func startExport(of album: Album) {
|
|
175
|
+
let identifier = "org.sample.photos.export.\(UUID().uuidString)"
|
|
176
|
+
BGTaskScheduler.shared.register(forTaskWithIdentifier: identifier, using: nil) { task in
|
|
177
|
+
guard let export = task as? BGContinuedProcessingTask else { return }
|
|
178
|
+
handleExport(export)
|
|
179
|
+
}
|
|
215
180
|
let request = BGContinuedProcessingTaskRequest(
|
|
216
|
-
identifier:
|
|
217
|
-
title: "Exporting
|
|
218
|
-
subtitle: "
|
|
181
|
+
identifier: identifier,
|
|
182
|
+
title: "Exporting \(album.name)",
|
|
183
|
+
subtitle: "Preparing"
|
|
219
184
|
)
|
|
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)")
|
|
185
|
+
request.strategy = .fail
|
|
186
|
+
if BGTaskScheduler.supportedResources.contains(.gpu) {
|
|
187
|
+
request.requiredResources = .gpu
|
|
188
|
+
}
|
|
189
|
+
guard (try? BGTaskScheduler.shared.submit(request)) != nil else {
|
|
190
|
+
showExportUnavailable()
|
|
191
|
+
return
|
|
230
192
|
}
|
|
231
193
|
}
|
|
232
194
|
|
|
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
|
-
)
|
|
195
|
+
func handleExport(_ task: BGContinuedProcessingTask) {
|
|
196
|
+
nonisolated(unsafe) let task = task
|
|
197
|
+
let work = Task { await runExport(task, items: ExportQueue.shared.pending) }
|
|
198
|
+
task.expirationHandler = {
|
|
199
|
+
work.cancel()
|
|
200
|
+
ExportQueue.shared.removePartialFiles()
|
|
249
201
|
}
|
|
202
|
+
}
|
|
250
203
|
|
|
204
|
+
func runExport(_ task: BGContinuedProcessingTask, items: [PhotoAsset]) async {
|
|
205
|
+
task.progress.totalUnitCount = Int64(items.count)
|
|
206
|
+
var done: Int64 = 0
|
|
207
|
+
for asset in items where !Task.isCancelled {
|
|
208
|
+
await render(asset)
|
|
209
|
+
done += 1
|
|
210
|
+
task.progress.completedUnitCount = done
|
|
211
|
+
task.updateTitle(task.title, subtitle: "Photo \(done) of \(items.count)")
|
|
212
|
+
}
|
|
251
213
|
task.setTaskCompleted(success: !Task.isCancelled)
|
|
252
214
|
}
|
|
253
215
|
```
|
|
254
216
|
|
|
255
|
-
|
|
217
|
+
When the task expires, the cancelled `Task` leaves the loop and
|
|
218
|
+
`runExport` reports `success: false`, so completion still happens exactly once. The
|
|
219
|
+
`nonisolated(unsafe)` copy in `handleExport` is there for the same reason as in
|
|
220
|
+
`perform`.
|
|
256
221
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
222
|
+
- Each job gets its own identifier under a permitted wildcard base, which
|
|
223
|
+
also keeps every registration unique.
|
|
224
|
+
- `earliestBeginDate` is ignored for this request type.
|
|
225
|
+
- `strategy` is `.queue` (start as soon as allowed) or `.fail` (reject the
|
|
226
|
+
submission if it cannot start now).
|
|
227
|
+
- GPU work needs both the `supportedResources` check and the entitlement for
|
|
228
|
+
background GPU use (key ending in `continued-processing.gpu`).
|
|
229
|
+
|
|
230
|
+
Strategy choice, cancellation and progress ranking are expanded in the
|
|
231
|
+
[patterns reference](references/background-task-patterns.md).
|
|
263
232
|
|
|
264
|
-
## Background URLSession
|
|
233
|
+
## Background URLSession
|
|
265
234
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
235
|
+
A session built with `URLSessionConfiguration.background(withIdentifier:)`
|
|
236
|
+
hands transfers to a system daemon, so they keep running while the app is
|
|
237
|
+
suspended or even terminated.
|
|
269
238
|
|
|
270
239
|
```swift
|
|
271
|
-
|
|
272
|
-
|
|
240
|
+
@MainActor
|
|
241
|
+
final class EpisodeDownloader: NSObject, URLSessionDownloadDelegate {
|
|
242
|
+
static let shared = EpisodeDownloader()
|
|
273
243
|
|
|
274
|
-
|
|
244
|
+
var finishEvents: (() -> Void)?
|
|
245
|
+
|
|
246
|
+
lazy var session: URLSession = {
|
|
275
247
|
let config = URLSessionConfiguration.background(
|
|
276
|
-
withIdentifier: "
|
|
248
|
+
withIdentifier: "org.sample.podcasts.downloads"
|
|
277
249
|
)
|
|
278
|
-
config.isDiscretionary = true
|
|
279
250
|
config.sessionSendsLaunchEvents = true
|
|
280
|
-
|
|
251
|
+
config.isDiscretionary = true
|
|
252
|
+
return URLSession(
|
|
253
|
+
configuration: config,
|
|
254
|
+
delegate: self,
|
|
255
|
+
delegateQueue: nil
|
|
256
|
+
)
|
|
281
257
|
}()
|
|
282
258
|
|
|
283
|
-
func
|
|
284
|
-
let
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
func urlSession(
|
|
290
|
-
_ session: URLSession,
|
|
291
|
-
downloadTask: URLSessionDownloadTask,
|
|
292
|
-
didFinishDownloadingTo location: URL
|
|
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)
|
|
259
|
+
func fetch(_ remote: URL, notBefore date: Date? = nil) {
|
|
260
|
+
let job = session.downloadTask(with: remote)
|
|
261
|
+
job.earliestBeginDate = date
|
|
262
|
+
job.resume()
|
|
299
263
|
}
|
|
300
264
|
|
|
301
|
-
func urlSession(
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
) {
|
|
306
|
-
if let error { print("Download failed: \(error)") }
|
|
265
|
+
nonisolated func urlSession(_ s: URLSession, downloadTask job: URLSessionDownloadTask, didFinishDownloadingTo tmp: URL) {
|
|
266
|
+
let name = job.response?.suggestedFilename ?? "episode-\(job.taskIdentifier)"
|
|
267
|
+
let target = URL.documentsDirectory.appending(path: name)
|
|
268
|
+
try? FileManager.default.moveItem(at: tmp, to: target)
|
|
307
269
|
}
|
|
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
270
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
self.backgroundSessionCompletionHandler?()
|
|
327
|
-
self.backgroundSessionCompletionHandler = nil
|
|
271
|
+
nonisolated func urlSession(_ s: URLSession, task job: URLSessionTask, didCompleteWithError error: (any Error)?) {
|
|
272
|
+
guard let error else { return }
|
|
273
|
+
Log.background.error("Transfer failed: \(error)")
|
|
328
274
|
}
|
|
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
275
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
Handle in AppDelegate:
|
|
347
|
-
|
|
348
|
-
```swift
|
|
349
|
-
func application(
|
|
350
|
-
_ application: UIApplication,
|
|
351
|
-
didReceiveRemoteNotification userInfo: [AnyHashable: Any],
|
|
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)
|
|
276
|
+
nonisolated func urlSessionDidFinishEvents(forBackgroundURLSession s: URLSession) {
|
|
277
|
+
Task { @MainActor in
|
|
278
|
+
self.finishEvents?()
|
|
279
|
+
self.finishEvents = nil
|
|
361
280
|
}
|
|
362
281
|
}
|
|
363
282
|
}
|
|
364
283
|
```
|
|
365
284
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
285
|
+
- The class is `@MainActor`, so the lazy session and the stored handler are
|
|
286
|
+
safe to touch from UIKit callbacks. The session calls its delegate on its
|
|
287
|
+
own queue, hence the `nonisolated` delegate methods.
|
|
288
|
+
- `isDiscretionary` lets the system wait for good conditions. With
|
|
289
|
+
`sessionSendsLaunchEvents` on, finished transfers launch the app again.
|
|
290
|
+
- A delegate is required (`delegateQueue: nil` is fine). The async and
|
|
291
|
+
completion-handler task APIs do not work with background sessions.
|
|
292
|
+
- The temporary file disappears when `didFinishDownloadingTo` returns, so move
|
|
293
|
+
it inside that method.
|
|
294
|
+
- Failures arrive in `urlSession(_:task:didCompleteWithError:)`.
|
|
373
295
|
|
|
374
|
-
|
|
296
|
+
When the system relaunches the app for session events, keep the handler it
|
|
297
|
+
gives you and call it once the delegate reports the events are done:
|
|
375
298
|
|
|
376
299
|
```swift
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
// <string>com.example.app.refresh</string>
|
|
383
|
-
```
|
|
384
|
-
|
|
385
|
-
### 2. Not calling setTaskCompleted(success:)
|
|
386
|
-
|
|
387
|
-
```swift
|
|
388
|
-
// DON'T: Return without marking completion -- system penalizes future scheduling
|
|
389
|
-
func handleRefresh(task: BGAppRefreshTask) {
|
|
390
|
-
Task {
|
|
391
|
-
let data = try await fetchData()
|
|
392
|
-
await store.update(data)
|
|
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
|
-
}
|
|
300
|
+
func application(_ app: UIApplication,
|
|
301
|
+
handleEventsForBackgroundURLSession id: String,
|
|
302
|
+
completionHandler done: @escaping () -> Void) {
|
|
303
|
+
EpisodeDownloader.shared.finishEvents = done
|
|
304
|
+
_ = EpisodeDownloader.shared.session
|
|
412
305
|
}
|
|
413
306
|
```
|
|
414
307
|
|
|
415
|
-
|
|
308
|
+
## Background pushes
|
|
416
309
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
Task { await heavyWork() }
|
|
421
|
-
// No expirationHandler -- system terminates ungracefully
|
|
422
|
-
}
|
|
310
|
+
A silent push wakes the app for a short time. The payload carries
|
|
311
|
+
`content-available: 1` plus your own keys. On the APNs request, set the
|
|
312
|
+
push type header to `background` and the priority header to `5`.
|
|
423
313
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
work.cancel()
|
|
429
|
-
task.setTaskCompleted(success: false)
|
|
430
|
-
}
|
|
314
|
+
```json
|
|
315
|
+
{
|
|
316
|
+
"aps": { "content-available": 1 },
|
|
317
|
+
"album-id": "A41"
|
|
431
318
|
}
|
|
432
319
|
```
|
|
433
320
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
// DON'T: Request refresh every minute -- system throttles aggressively
|
|
438
|
-
request.earliestBeginDate = Date(timeIntervalSinceNow: 60)
|
|
439
|
-
|
|
440
|
-
// DO: Use reasonable intervals (15+ minutes for refresh)
|
|
441
|
-
request.earliestBeginDate = Date(timeIntervalSinceNow: 15 * 60)
|
|
442
|
-
// earliestBeginDate is a hint -- the system chooses actual launch time
|
|
443
|
-
```
|
|
444
|
-
|
|
445
|
-
### 5. Over-relying on background time
|
|
321
|
+
Delivery is low priority and never guaranteed. Keep it to two or three per hour
|
|
322
|
+
at most. Enable Remote notifications under Background Modes and call
|
|
323
|
+
`UIApplication.shared.registerForRemoteNotifications()`.
|
|
446
324
|
|
|
447
325
|
```swift
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
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)
|
|
326
|
+
func application(_ app: UIApplication,
|
|
327
|
+
didReceiveRemoteNotification payload: [AnyHashable: Any],
|
|
328
|
+
fetchCompletionHandler reply: @escaping (UIBackgroundFetchResult) -> Void) {
|
|
329
|
+
guard let albumID = payload["album-id"] as? String else {
|
|
330
|
+
return reply(.noData)
|
|
462
331
|
}
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
332
|
+
Task {
|
|
333
|
+
let outcome: Result<Bool, any Error> = await LibrarySync.shared.refreshAlbum(albumID)
|
|
334
|
+
switch outcome {
|
|
335
|
+
case .success(true): reply(.newData)
|
|
336
|
+
case .success(false): reply(.noData)
|
|
337
|
+
case .failure: reply(.failed)
|
|
338
|
+
}
|
|
466
339
|
}
|
|
467
340
|
}
|
|
468
341
|
```
|
|
469
342
|
|
|
470
|
-
##
|
|
471
|
-
|
|
472
|
-
-
|
|
473
|
-
-
|
|
474
|
-
|
|
475
|
-
-
|
|
476
|
-
|
|
477
|
-
-
|
|
478
|
-
|
|
479
|
-
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
- [ ]
|
|
486
|
-
- [ ]
|
|
487
|
-
- [ ]
|
|
343
|
+
## Common mistakes
|
|
344
|
+
|
|
345
|
+
- **Identifier missing from Info.plist.** `submit` throws `.notPermitted`.
|
|
346
|
+
- **Skipping `setTaskCompleted(success:)`.** The system schedules the app less
|
|
347
|
+
often afterwards. Complete on every path, errors included.
|
|
348
|
+
- **No expiration handler.** The task is killed abruptly instead of stopping
|
|
349
|
+
cleanly. Cancel the work and complete from the handler.
|
|
350
|
+
- **Asking too often.** A refresh every minute gets throttled hard. Use 15
|
|
351
|
+
minutes or more, and remember `earliestBeginDate` is a hint.
|
|
352
|
+
- **Too much work per run.** A ten-minute job inside a refresh task will not
|
|
353
|
+
finish. Split work into small cancellable batches, call
|
|
354
|
+
`try Task.checkCancellation()` between them and save progress.
|
|
355
|
+
|
|
356
|
+
## Review checklist
|
|
357
|
+
|
|
358
|
+
- [ ] Every identifier is in `BGTaskSchedulerPermittedIdentifiers`
|
|
359
|
+
- [ ] `UIBackgroundModes` has `fetch` and/or `processing` as needed
|
|
360
|
+
- [ ] Refresh and processing handlers are registered before launch completes;
|
|
361
|
+
continued processing handlers are registered before their `submit`
|
|
362
|
+
- [ ] `setTaskCompleted(success:)` runs on every path
|
|
363
|
+
- [ ] `expirationHandler` cancels in-flight work
|
|
364
|
+
- [ ] The handler schedules the next task
|
|
365
|
+
- [ ] `earliestBeginDate` is sensible and read as a lower bound only
|
|
366
|
+
- [ ] Background `URLSession` uses a delegate, not async or closure APIs
|
|
367
|
+
- [ ] Downloaded files are moved inside `didFinishDownloadingTo`
|
|
368
|
+
- [ ] `handleEventsForBackgroundURLSession` stores its handler and it is called later
|
|
369
|
+
- [ ] Silent push payload has `content-available: 1`
|
|
370
|
+
- [ ] APNs headers: push type `background`, priority `5`
|
|
371
|
+
- [ ] `fetchCompletionHandler` is called promptly with the right result
|
|
372
|
+
- [ ] Continued processing tasks report progress through `ProgressReporting`
|
|
373
|
+
- [ ] Work is incremental and checks cancellation (`Task.checkCancellation()`)
|
|
374
|
+
- [ ] Handlers never block on synchronous work
|
|
488
375
|
|
|
489
376
|
## References
|
|
490
377
|
|
|
491
|
-
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
378
|
+
- [Background task patterns](references/background-task-patterns.md):
|
|
379
|
+
debugging, error codes, checkpointing, URLSession extras, push limits,
|
|
380
|
+
SwiftUI `.backgroundTask`, continued-processing details
|
|
381
|
+
- Apple documentation:
|
|
382
|
+
[scheduler](https://developer.apple.com/documentation/backgroundtasks/bgtaskscheduler),
|
|
383
|
+
[refresh task](https://developer.apple.com/documentation/backgroundtasks/bgapprefreshtask),
|
|
384
|
+
[processing task](https://developer.apple.com/documentation/backgroundtasks/bgprocessingtask),
|
|
385
|
+
[continued processing task](https://developer.apple.com/documentation/backgroundtasks/bgcontinuedprocessingtask) (iOS 26+),
|
|
386
|
+
[continued processing request](https://developer.apple.com/documentation/backgroundtasks/bgcontinuedprocessingtaskrequest) (iOS 26+),
|
|
387
|
+
[keeping content fresh with background tasks](https://developer.apple.com/documentation/uikit/using-background-tasks-to-update-your-app),
|
|
388
|
+
[long-running work](https://developer.apple.com/documentation/backgroundtasks/performing-long-running-tasks-on-ios-and-ipados)
|