@mmerterden/multi-agent-pipeline 20.7.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 +9 -0
- package/LICENSE +0 -10
- package/docs/facts.json +1 -1
- package/manifest.json +266 -267
- package/package.json +2 -2
- package/pipeline/scripts/_notices.mjs +1 -1
- package/pipeline/skills/.skill-manifest.json +68 -68
- 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 +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/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 +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/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 +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/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/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/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/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/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 +152 -310
- package/pipeline/skills/shared/external/weatherkit/references/weatherkit-patterns.md +288 -407
- 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,504 +1,308 @@
|
|
|
1
|
-
# Core Data
|
|
1
|
+
# Core Data and SwiftData together
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
For a question about running both frameworks or moving from one to the other,
|
|
4
|
+
start at [Sharing one store](#sharing-one-store). The Core Data stack section
|
|
5
|
+
is context for reading an existing app, not a Core Data tutorial.
|
|
6
6
|
|
|
7
7
|
## Contents
|
|
8
8
|
|
|
9
|
-
- [
|
|
10
|
-
- [
|
|
11
|
-
- [
|
|
9
|
+
- [The Core Data side at a glance](#the-core-data-side-at-a-glance)
|
|
10
|
+
- [Sharing one store](#sharing-one-store)
|
|
11
|
+
- [Migrating an app to SwiftData](#migrating-an-app-to-swiftdata)
|
|
12
|
+
- [Checklist](#checklist)
|
|
12
13
|
|
|
13
|
-
##
|
|
14
|
+
## The Core Data side at a glance
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
Docs: [NSPersistentContainer](https://sosumi.ai/documentation/coredata/nspersistentcontainer),
|
|
19
|
-
[Setting up a Core Data stack](https://sosumi.ai/documentation/coredata/setting-up-a-core-data-stack)
|
|
20
|
-
|
|
21
|
-
### NSPersistentContainer Setup
|
|
22
|
-
|
|
23
|
-
`NSPersistentContainer` encapsulates the Core Data stack: the managed object
|
|
24
|
-
model, the persistent store coordinator, and the managed object context.
|
|
16
|
+
Core Data remains fully supported, and many apps keep it. The pieces you will
|
|
17
|
+
meet when reading such an app:
|
|
25
18
|
|
|
26
19
|
```swift
|
|
27
20
|
import CoreData
|
|
28
21
|
|
|
29
|
-
final class
|
|
30
|
-
static let shared = CoreDataStack()
|
|
31
|
-
|
|
22
|
+
final class LibraryStack {
|
|
32
23
|
let container: NSPersistentContainer
|
|
33
24
|
|
|
34
|
-
|
|
35
|
-
//
|
|
36
|
-
container = NSPersistentContainer(name: "
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
25
|
+
init() {
|
|
26
|
+
// "Library" is also the name of Library.xcdatamodeld.
|
|
27
|
+
container = NSPersistentContainer(name: "Library")
|
|
28
|
+
let main = container.viewContext
|
|
29
|
+
main.automaticallyMergesChangesFromParent = true
|
|
30
|
+
main.mergePolicy = NSMergePolicy.mergeByPropertyObjectTrump
|
|
31
|
+
container.loadPersistentStores { description, loadError in
|
|
32
|
+
guard let loadError else { return }
|
|
33
|
+
fatalError("Could not open \(description.url?.lastPathComponent ?? "store"): \(loadError)")
|
|
42
34
|
}
|
|
43
|
-
|
|
44
|
-
// Automatically merge changes from background contexts
|
|
45
|
-
container.viewContext.automaticallyMergesChangesFromParent = true
|
|
46
|
-
container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/// The main-thread context for UI reads
|
|
50
|
-
var viewContext: NSManagedObjectContext {
|
|
51
|
-
container.viewContext
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/// A new background context for writes
|
|
55
|
-
func newBackgroundContext() -> NSManagedObjectContext {
|
|
56
|
-
container.newBackgroundContext()
|
|
57
35
|
}
|
|
58
36
|
}
|
|
59
37
|
```
|
|
60
38
|
|
|
61
|
-
|
|
39
|
+
- `viewContext` runs on the main queue and serves reads and UI.
|
|
40
|
+
- Writes and bulk work use `newBackgroundContext()` inside
|
|
41
|
+
`await context.perform { }`.
|
|
62
42
|
|
|
63
|
-
|
|
64
|
-
background contexts for writes and batch operations.
|
|
43
|
+
Fetching:
|
|
65
44
|
|
|
66
45
|
```swift
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
let
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
return try context.fetch(request)
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Writing on a background context
|
|
76
|
-
func createTrip(name: String, destination: String) async throws {
|
|
77
|
-
let context = CoreDataStack.shared.newBackgroundContext()
|
|
78
|
-
try await context.perform {
|
|
79
|
-
let trip = CDTrip(context: context)
|
|
80
|
-
trip.name = name
|
|
81
|
-
trip.destination = destination
|
|
82
|
-
trip.startDate = Date.now
|
|
83
|
-
trip.id = UUID()
|
|
84
|
-
try context.save()
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
### NSFetchRequest with NSPredicate and NSSortDescriptor
|
|
90
|
-
|
|
91
|
-
```swift
|
|
92
|
-
import CoreData
|
|
93
|
-
|
|
94
|
-
func fetchUpcomingTrips(destination: String) throws -> [CDTrip] {
|
|
95
|
-
let context = CoreDataStack.shared.viewContext
|
|
96
|
-
let request: NSFetchRequest<CDTrip> = CDTrip.fetchRequest()
|
|
97
|
-
|
|
98
|
-
// Predicate: filter by destination and future start date
|
|
99
|
-
request.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [
|
|
100
|
-
NSPredicate(format: "destination ==[cd] %@", destination),
|
|
101
|
-
NSPredicate(format: "startDate > %@", Date.now as NSDate)
|
|
102
|
-
])
|
|
103
|
-
|
|
104
|
-
// Sort: newest first
|
|
105
|
-
request.sortDescriptors = [
|
|
106
|
-
NSSortDescriptor(keyPath: \CDTrip.startDate, ascending: false)
|
|
46
|
+
func overdueLoans(for member: String, in context: NSManagedObjectContext) throws -> [CDLoan] {
|
|
47
|
+
let request = NSFetchRequest<CDLoan>(entityName: "Loan")
|
|
48
|
+
let conditions = [
|
|
49
|
+
NSPredicate(format: "memberName ==[cd] %@", member),
|
|
50
|
+
NSPredicate(format: "dueAt < %@", Date() as NSDate)
|
|
107
51
|
]
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
request.fetchLimit =
|
|
111
|
-
request.relationshipKeyPathsForPrefetching = ["
|
|
112
|
-
|
|
52
|
+
request.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: conditions)
|
|
53
|
+
request.sortDescriptors = [NSSortDescriptor(keyPath: \CDLoan.dueAt, ascending: true)]
|
|
54
|
+
request.fetchLimit = 50
|
|
55
|
+
request.relationshipKeyPathsForPrefetching = ["book"]
|
|
113
56
|
return try context.fetch(request)
|
|
114
57
|
}
|
|
115
58
|
|
|
116
|
-
|
|
117
|
-
func countFavoriteTrips() throws -> Int {
|
|
118
|
-
let request: NSFetchRequest<CDTrip> = CDTrip.fetchRequest()
|
|
119
|
-
request.predicate = NSPredicate(format: "isFavorite == YES")
|
|
120
|
-
return try CoreDataStack.shared.viewContext.count(for: request)
|
|
121
|
-
}
|
|
59
|
+
let openCount = try context.count(for: NSFetchRequest<CDLoan>(entityName: "Loan"))
|
|
122
60
|
```
|
|
123
61
|
|
|
124
|
-
|
|
62
|
+
Saving, with failures turned into app errors:
|
|
125
63
|
|
|
126
64
|
```swift
|
|
127
|
-
|
|
128
|
-
guard context.hasChanges else { return }
|
|
65
|
+
enum LibraryStoreError: Error { case duplicate, missingField, other(Error) }
|
|
129
66
|
|
|
67
|
+
func commit(_ moc: NSManagedObjectContext) throws {
|
|
68
|
+
if !moc.hasChanges { return }
|
|
130
69
|
do {
|
|
131
|
-
try
|
|
132
|
-
} catch {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
switch nsError.code {
|
|
139
|
-
case NSManagedObjectConstraintMergeError:
|
|
140
|
-
// Unique constraint violation -- handle merge conflict
|
|
141
|
-
throw PersistenceError.constraintViolation(nsError)
|
|
142
|
-
case NSValidationMissingMandatoryPropertyError:
|
|
143
|
-
// Required property is nil
|
|
144
|
-
throw PersistenceError.validationFailed(nsError)
|
|
145
|
-
default:
|
|
146
|
-
throw PersistenceError.saveFailed(nsError)
|
|
147
|
-
}
|
|
70
|
+
try moc.save()
|
|
71
|
+
} catch let error as NSError {
|
|
72
|
+
moc.rollback()
|
|
73
|
+
switch error.code {
|
|
74
|
+
case NSManagedObjectConstraintMergeError: throw LibraryStoreError.duplicate
|
|
75
|
+
case NSValidationMissingMandatoryPropertyError: throw LibraryStoreError.missingField
|
|
76
|
+
default: throw LibraryStoreError.other(error)
|
|
148
77
|
}
|
|
149
|
-
throw error
|
|
150
78
|
}
|
|
151
79
|
}
|
|
152
|
-
|
|
153
|
-
enum PersistenceError: Error {
|
|
154
|
-
case constraintViolation(NSError)
|
|
155
|
-
case validationFailed(NSError)
|
|
156
|
-
case saveFailed(NSError)
|
|
157
|
-
}
|
|
158
80
|
```
|
|
159
81
|
|
|
160
|
-
|
|
82
|
+
Bulk insert (iOS 13+) bypasses the context, so push the result into the view
|
|
83
|
+
context afterwards:
|
|
161
84
|
|
|
162
85
|
```swift
|
|
163
|
-
func
|
|
164
|
-
let context =
|
|
165
|
-
try await context.perform {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
[
|
|
171
|
-
"id": record.id,
|
|
172
|
-
"name": record.name,
|
|
173
|
-
"destination": record.destination,
|
|
174
|
-
"startDate": record.startDate
|
|
175
|
-
] as [String: Any]
|
|
176
|
-
}
|
|
177
|
-
)
|
|
178
|
-
batchInsert.resultType = .count
|
|
179
|
-
let result = try context.execute(batchInsert) as? NSBatchInsertResult
|
|
180
|
-
print("Inserted \(result?.result as? Int ?? 0) trips")
|
|
181
|
-
|
|
182
|
-
// Merge changes into viewContext
|
|
183
|
-
NSManagedObjectContext.mergeChanges(
|
|
184
|
-
fromRemoteContextSave: [NSInsertedObjectsKey: []],
|
|
185
|
-
into: [CoreDataStack.shared.viewContext]
|
|
186
|
-
)
|
|
86
|
+
func importBooks(_ rows: [[String: any Sendable]], stack: LibraryStack) async throws {
|
|
87
|
+
let context = stack.container.newBackgroundContext()
|
|
88
|
+
let inserted: [NSManagedObjectID] = try await context.perform {
|
|
89
|
+
let insert = NSBatchInsertRequest(entity: CDBook.entity(), objects: rows)
|
|
90
|
+
insert.resultType = .objectIDs
|
|
91
|
+
let outcome = try context.execute(insert)
|
|
92
|
+
return (outcome as? NSBatchInsertResult)?.result as? [NSManagedObjectID] ?? []
|
|
187
93
|
}
|
|
94
|
+
let changes = [NSInsertedObjectsKey: inserted]
|
|
95
|
+
NSManagedObjectContext.mergeChanges(fromRemoteContextSave: changes,
|
|
96
|
+
into: [stack.container.viewContext])
|
|
188
97
|
}
|
|
189
98
|
```
|
|
190
99
|
|
|
191
|
-
|
|
100
|
+
Use `resultType = .count` when you only need how many rows went in; request
|
|
101
|
+
`.objectIDs`, as above, when you want to merge the inserted objects into other
|
|
102
|
+
contexts.
|
|
192
103
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
104
|
+
Apple documentation: the `NSPersistentContainer` reference
|
|
105
|
+
(https://developer.apple.com/documentation/coredata/nspersistentcontainer) and
|
|
106
|
+
the Core Data stack setup article in the same section.
|
|
196
107
|
|
|
197
|
-
|
|
108
|
+
## Sharing one store
|
|
198
109
|
|
|
199
|
-
|
|
110
|
+
Both frameworks can open the same SQLite file, which lets an app move one
|
|
111
|
+
screen at a time.
|
|
200
112
|
|
|
201
|
-
|
|
202
|
-
Core Data `.xcdatamodeld` and SwiftData `@Model` classes must describe the
|
|
203
|
-
same entities and properties.
|
|
113
|
+
Point SwiftData at the file Core Data already uses:
|
|
204
114
|
|
|
205
115
|
```swift
|
|
206
|
-
import SwiftData
|
|
207
|
-
import CoreData
|
|
208
|
-
|
|
209
|
-
// 1. Determine the store URL that Core Data already uses
|
|
210
116
|
let storeURL = NSPersistentContainer.defaultDirectoryURL()
|
|
211
|
-
.appendingPathComponent("
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
let config = ModelConfiguration(
|
|
215
|
-
"MyAppModel",
|
|
216
|
-
url: storeURL
|
|
217
|
-
)
|
|
218
|
-
|
|
219
|
-
let container = try ModelContainer(
|
|
220
|
-
for: Trip.self,
|
|
221
|
-
configurations: config
|
|
222
|
-
)
|
|
223
|
-
```
|
|
224
|
-
|
|
225
|
-
### ModelConfiguration Pointing to Existing Core Data Store
|
|
226
|
-
|
|
227
|
-
Key rules for coexistence:
|
|
228
|
-
|
|
229
|
-
1. The `@Model` class name must match the Core Data entity name.
|
|
230
|
-
2. Property names and types must match exactly.
|
|
231
|
-
3. Use `@Attribute(originalName:)` if you renamed properties.
|
|
232
|
-
4. Both stacks should use the same store file.
|
|
233
|
-
|
|
234
|
-
```swift
|
|
235
|
-
// Core Data entity: CDTrip (entity name "Trip" in .xcdatamodeld)
|
|
236
|
-
// Attributes: name (String), destination (String), startDate (Date),
|
|
237
|
-
// isFavorite (Boolean), imageData (Binary Data)
|
|
238
|
-
|
|
239
|
-
// Matching SwiftData model
|
|
240
|
-
@Model
|
|
241
|
-
class Trip {
|
|
242
|
-
var name: String
|
|
243
|
-
var destination: String
|
|
244
|
-
var startDate: Date
|
|
245
|
-
var isFavorite: Bool = false
|
|
246
|
-
@Attribute(.externalStorage) var imageData: Data?
|
|
247
|
-
|
|
248
|
-
init(name: String, destination: String, startDate: Date) {
|
|
249
|
-
self.name = name
|
|
250
|
-
self.destination = destination
|
|
251
|
-
self.startDate = startDate
|
|
252
|
-
}
|
|
253
|
-
}
|
|
117
|
+
.appendingPathComponent("Library.sqlite")
|
|
118
|
+
let sharedStore = ModelConfiguration("Library", url: storeURL)
|
|
119
|
+
let swiftDataSide = try ModelContainer(for: Book.self, Loan.self, configurations: sharedStore)
|
|
254
120
|
```
|
|
255
121
|
|
|
256
|
-
|
|
122
|
+
Rules for the shared store:
|
|
257
123
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
)
|
|
273
|
-
}
|
|
274
|
-
}
|
|
124
|
+
- Each `@Model` class name equals the Core Data entity name. Name the Core Data
|
|
125
|
+
classes differently (for example `CDBook`) so the Swift types do not collide.
|
|
126
|
+
- Property names and types match exactly. Where the Swift name has to differ,
|
|
127
|
+
use `@Attribute(originalName:)`.
|
|
128
|
+
- Both stacks use the same file.
|
|
129
|
+
- Turn on persistent history (`NSPersistentHistoryTrackingKey`) in the Core
|
|
130
|
+
Data store description.
|
|
131
|
+
- Only one framework writes a given entity at a time. Pick the writer per
|
|
132
|
+
entity and keep it there until that entity moves.
|
|
133
|
+
- `automaticallyMergesChangesFromParent` and
|
|
134
|
+
`NSPersistentStoreRemoteChangeNotification` let each side notice the other's
|
|
135
|
+
writes.
|
|
136
|
+
- A mismatch between the `.xcdatamodeld` and the `@Model` classes crashes at
|
|
137
|
+
load time. Test the pairing thoroughly.
|
|
275
138
|
|
|
276
|
-
|
|
277
|
-
// Phase 3: Migrate remaining Core Data code to SwiftData
|
|
278
|
-
// Phase 4: Remove Core Data stack and .xcdatamodeld
|
|
279
|
-
```
|
|
139
|
+
A phased move:
|
|
280
140
|
|
|
281
|
-
|
|
141
|
+
1. Core Data keeps writing; new screens read through SwiftData.
|
|
142
|
+
2. New features read and write with SwiftData.
|
|
143
|
+
3. Remaining code moves over entity by entity.
|
|
144
|
+
4. Delete the Core Data stack and the `.xcdatamodeld`.
|
|
282
145
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
- Core Data's `automaticallyMergesChangesFromParent` and
|
|
286
|
-
`NSPersistentStoreRemoteChangeNotification` help detect changes from the
|
|
287
|
-
other stack.
|
|
288
|
-
- Test thoroughly -- schema mismatches between the `.xcdatamodeld` and
|
|
289
|
-
`@Model` cause crashes.
|
|
146
|
+
Apple documentation: the SwiftData adoption article for Core Data apps, in the
|
|
147
|
+
Core Data section of the developer documentation.
|
|
290
148
|
|
|
291
|
-
##
|
|
149
|
+
## Migrating an app to SwiftData
|
|
292
150
|
|
|
293
|
-
### Step 1:
|
|
151
|
+
### Step 1: write one model per entity
|
|
294
152
|
|
|
295
|
-
|
|
296
|
-
|
|
153
|
+
To-one relationships become optional references; to-many become arrays. Give
|
|
154
|
+
both ends an inverse.
|
|
297
155
|
|
|
298
156
|
```swift
|
|
299
|
-
// Core Data entity "Article"
|
|
300
|
-
// Attributes: id (UUID), title (String), body (String),
|
|
301
|
-
// createdAt (Date), isDraft (Boolean)
|
|
302
|
-
// Relationships: author (to-one → Author), tags (to-many → Tag)
|
|
303
|
-
|
|
304
157
|
@Model
|
|
305
|
-
class
|
|
306
|
-
@Attribute(.unique) var id: UUID
|
|
158
|
+
final class Book {
|
|
307
159
|
var title: String
|
|
308
|
-
var
|
|
309
|
-
var
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
@Relationship(deleteRule: .nullify, inverse: \Author.articles)
|
|
313
|
-
var author: Author?
|
|
160
|
+
var isbn: String
|
|
161
|
+
var publishedOn: Date?
|
|
162
|
+
@Relationship(deleteRule: .nullify, inverse: \Loan.book)
|
|
163
|
+
var loans: [Loan] = []
|
|
314
164
|
|
|
315
|
-
|
|
316
|
-
var tags: [Tag] = []
|
|
317
|
-
|
|
318
|
-
init(id: UUID = UUID(), title: String, body: String, createdAt: Date = .now) {
|
|
319
|
-
self.id = id
|
|
165
|
+
init(title: String, isbn: String) {
|
|
320
166
|
self.title = title
|
|
321
|
-
self.
|
|
322
|
-
self.createdAt = createdAt
|
|
167
|
+
self.isbn = isbn
|
|
323
168
|
}
|
|
324
169
|
}
|
|
325
170
|
|
|
326
171
|
@Model
|
|
327
|
-
class
|
|
328
|
-
|
|
329
|
-
var
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
self.
|
|
335
|
-
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
@Model
|
|
339
|
-
class Tag {
|
|
340
|
-
@Attribute(.unique) var id: UUID
|
|
341
|
-
var name: String
|
|
342
|
-
var articles: [Article] = []
|
|
343
|
-
|
|
344
|
-
init(id: UUID = UUID(), name: String) {
|
|
345
|
-
self.id = id
|
|
346
|
-
self.name = name
|
|
172
|
+
final class Loan {
|
|
173
|
+
var memberName: String
|
|
174
|
+
var dueAt: Date
|
|
175
|
+
@Relationship(deleteRule: .nullify)
|
|
176
|
+
var book: Book?
|
|
177
|
+
|
|
178
|
+
init(memberName: String, dueAt: Date) {
|
|
179
|
+
self.memberName = memberName
|
|
180
|
+
self.dueAt = dueAt
|
|
347
181
|
}
|
|
348
182
|
}
|
|
349
183
|
```
|
|
350
184
|
|
|
351
|
-
|
|
185
|
+
Type mapping, SwiftData type first:
|
|
352
186
|
|
|
353
|
-
| Core Data
|
|
187
|
+
| Swift type in the `@Model` | Core Data attribute it replaces |
|
|
354
188
|
|---|---|
|
|
355
|
-
| String | String |
|
|
356
|
-
|
|
|
357
|
-
| Integer 16
|
|
358
|
-
|
|
|
359
|
-
|
|
|
360
|
-
|
|
|
361
|
-
| UUID | UUID |
|
|
362
|
-
|
|
|
363
|
-
|
|
|
364
|
-
|
|
|
365
|
-
|
|
|
366
|
-
| To-many relationship |
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
189
|
+
| `String` | String |
|
|
190
|
+
| `Bool` | Boolean |
|
|
191
|
+
| `Int` | any of the Integer types (16, 32 or 64 bit) |
|
|
192
|
+
| `Double` / `Float` | Double / Float |
|
|
193
|
+
| `Decimal` | Decimal |
|
|
194
|
+
| `Date` | Date |
|
|
195
|
+
| `UUID` | UUID |
|
|
196
|
+
| `URL` | URI |
|
|
197
|
+
| `Data` | Binary Data |
|
|
198
|
+
| A `Codable` struct or enum, stored as a composite attribute | Transformable |
|
|
199
|
+
| Optional reference to another `@Model` | To-one relationship |
|
|
200
|
+
| `[RelatedModel]` array | To-many relationship |
|
|
201
|
+
|
|
202
|
+
Compatible `Codable` structs and enums are storable in any SwiftData release;
|
|
203
|
+
there is no separate OS threshold for storing them.
|
|
204
|
+
|
|
205
|
+
### Step 2: open the existing store
|
|
206
|
+
|
|
207
|
+
If the SwiftData models match the Core Data model exactly, the store opens with
|
|
208
|
+
no migration. If you also want to change the schema, describe the current shape
|
|
209
|
+
as the first `VersionedSchema` and migrate from there:
|
|
372
210
|
|
|
373
211
|
```swift
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
// For schema differences, define versioned schemas:
|
|
378
|
-
enum SchemaV1: VersionedSchema {
|
|
379
|
-
static var versionIdentifier = Schema.Version(1, 0, 0)
|
|
380
|
-
static var models: [any PersistentModel.Type] { [Article.self, Author.self] }
|
|
212
|
+
enum LibraryV1: VersionedSchema {
|
|
213
|
+
static let versionIdentifier: Schema.Version = .init(1, 0, 0)
|
|
381
214
|
|
|
382
|
-
|
|
383
|
-
|
|
215
|
+
static var models: [any PersistentModel.Type] { [Book.self, Loan.self] }
|
|
216
|
+
@Model final class Book {
|
|
384
217
|
var title: String
|
|
385
|
-
var
|
|
386
|
-
|
|
387
|
-
init(id: UUID, title: String, body: String, createdAt: Date) {
|
|
388
|
-
self.id = id; self.title = title
|
|
389
|
-
self.body = body; self.createdAt = createdAt
|
|
390
|
-
}
|
|
218
|
+
var isbn: String
|
|
219
|
+
init(title: String, isbn: String) { self.title = title; self.isbn = isbn }
|
|
391
220
|
}
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
var
|
|
395
|
-
|
|
396
|
-
init(id: UUID, name: String) { self.id = id; self.name = name }
|
|
221
|
+
@Model final class Loan {
|
|
222
|
+
var memberName: String
|
|
223
|
+
var dueAt: Date
|
|
224
|
+
init(memberName: String, dueAt: Date) { self.memberName = memberName; self.dueAt = dueAt }
|
|
397
225
|
}
|
|
398
226
|
}
|
|
399
227
|
|
|
400
|
-
enum
|
|
401
|
-
static
|
|
402
|
-
static var models: [any PersistentModel.Type] { [Article.self, Author.self, Tag.self] }
|
|
228
|
+
enum LibraryV2: VersionedSchema {
|
|
229
|
+
static let versionIdentifier: Schema.Version = .init(2, 0, 0)
|
|
403
230
|
|
|
404
|
-
|
|
405
|
-
|
|
231
|
+
static var models: [any PersistentModel.Type] { [Book.self, Loan.self, Shelf.self] }
|
|
232
|
+
@Model final class Book {
|
|
406
233
|
var title: String
|
|
407
|
-
var
|
|
408
|
-
var
|
|
409
|
-
|
|
410
|
-
init(id: UUID, title: String, body: String, createdAt: Date) {
|
|
411
|
-
self.id = id; self.title = title
|
|
412
|
-
self.body = body; self.createdAt = createdAt
|
|
413
|
-
}
|
|
234
|
+
var isbn: String
|
|
235
|
+
var isArchived: Bool = false
|
|
236
|
+
init(title: String, isbn: String) { self.title = title; self.isbn = isbn }
|
|
414
237
|
}
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
var
|
|
418
|
-
|
|
419
|
-
init(id: UUID, name: String) { self.id = id; self.name = name }
|
|
238
|
+
@Model final class Loan {
|
|
239
|
+
var memberName: String
|
|
240
|
+
var dueAt: Date
|
|
241
|
+
init(memberName: String, dueAt: Date) { self.memberName = memberName; self.dueAt = dueAt }
|
|
420
242
|
}
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
var name: String
|
|
425
|
-
init(id: UUID, name: String) { self.id = id; self.name = name }
|
|
243
|
+
@Model final class Shelf {
|
|
244
|
+
var code: String
|
|
245
|
+
init(code: String) { self.code = code }
|
|
426
246
|
}
|
|
427
247
|
}
|
|
428
248
|
|
|
429
|
-
enum
|
|
430
|
-
static var schemas: [any VersionedSchema.Type] { [
|
|
249
|
+
enum LibraryMigration: SchemaMigrationPlan {
|
|
250
|
+
static var schemas: [any VersionedSchema.Type] { [LibraryV1.self, LibraryV2.self] }
|
|
431
251
|
static var stages: [MigrationStage] {
|
|
432
|
-
[
|
|
252
|
+
[.lightweight(fromVersion: LibraryV1.self, toVersion: LibraryV2.self)]
|
|
433
253
|
}
|
|
434
254
|
}
|
|
435
255
|
```
|
|
436
256
|
|
|
437
|
-
### Step 3:
|
|
257
|
+
### Step 3: test against a real store
|
|
438
258
|
|
|
439
|
-
|
|
259
|
+
Copy a fixture store taken from a real install, including its `-wal` and
|
|
260
|
+
`-shm` files, open it through the migration plan, and check the data.
|
|
440
261
|
|
|
441
262
|
```swift
|
|
442
|
-
import
|
|
263
|
+
import Testing
|
|
443
264
|
import SwiftData
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
// Copy WAL and SHM if they exist
|
|
460
|
-
for ext in ["-wal", "-shm"] {
|
|
461
|
-
let src = sourceURL.deletingLastPathComponent()
|
|
462
|
-
.appendingPathComponent("TestStore.sqlite\(ext)")
|
|
463
|
-
if FileManager.default.fileExists(atPath: src.path) {
|
|
464
|
-
try FileManager.default.copyItem(
|
|
465
|
-
at: src,
|
|
466
|
-
to: tempDir.appendingPathComponent("TestStore.sqlite\(ext)")
|
|
467
|
-
)
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
// 2. Open with SwiftData
|
|
472
|
-
let config = ModelConfiguration(url: destURL)
|
|
473
|
-
let container = try ModelContainer(
|
|
474
|
-
for: SchemaV2.Article.self,
|
|
475
|
-
migrationPlan: ArticleMigrationPlan.self,
|
|
476
|
-
configurations: config
|
|
477
|
-
)
|
|
478
|
-
|
|
479
|
-
// 3. Verify data survived migration
|
|
480
|
-
let context = ModelContext(container)
|
|
481
|
-
let articles = try context.fetch(FetchDescriptor<SchemaV2.Article>())
|
|
482
|
-
XCTAssertFalse(articles.isEmpty, "Migration should preserve existing articles")
|
|
483
|
-
|
|
484
|
-
// 4. Verify new properties have defaults
|
|
485
|
-
for article in articles {
|
|
486
|
-
XCTAssertTrue(article.isDraft, "New isDraft property should default to true")
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
// Cleanup
|
|
490
|
-
try FileManager.default.removeItem(at: tempDir)
|
|
265
|
+
import Foundation
|
|
266
|
+
|
|
267
|
+
@Test func legacyLibraryStoreMigrates() throws {
|
|
268
|
+
let fm = FileManager.default
|
|
269
|
+
let scratch = fm.temporaryDirectory.appending(path: "migration-\(UUID().uuidString)")
|
|
270
|
+
try fm.createDirectory(at: scratch, withIntermediateDirectories: true)
|
|
271
|
+
defer { try? fm.removeItem(at: scratch) }
|
|
272
|
+
|
|
273
|
+
for suffix in ["", "-wal", "-shm"] {
|
|
274
|
+
guard let source = Bundle.module.url(forResource: "Library.sqlite\(suffix)",
|
|
275
|
+
withExtension: nil) else { continue }
|
|
276
|
+
try fm.copyItem(
|
|
277
|
+
at: source,
|
|
278
|
+
to: scratch.appendingPathComponent("Library.sqlite\(suffix)"))
|
|
491
279
|
}
|
|
280
|
+
|
|
281
|
+
let config = ModelConfiguration(url: scratch.appendingPathComponent("Library.sqlite"))
|
|
282
|
+
let container = try ModelContainer(
|
|
283
|
+
for: LibraryV2.Book.self, LibraryV2.Loan.self, LibraryV2.Shelf.self,
|
|
284
|
+
migrationPlan: LibraryMigration.self,
|
|
285
|
+
configurations: config
|
|
286
|
+
)
|
|
287
|
+
let context = ModelContext(container)
|
|
288
|
+
let books = try context.fetch(FetchDescriptor<LibraryV2.Book>())
|
|
289
|
+
|
|
290
|
+
#expect(!books.isEmpty)
|
|
291
|
+
#expect(books.allSatisfy { !$0.isArchived })
|
|
492
292
|
}
|
|
493
293
|
```
|
|
494
294
|
|
|
495
|
-
|
|
295
|
+
Run this against copies of production data before every release that touches
|
|
296
|
+
the schema.
|
|
297
|
+
|
|
298
|
+
## Checklist
|
|
496
299
|
|
|
497
|
-
- [ ]
|
|
498
|
-
- [ ]
|
|
499
|
-
- [ ]
|
|
500
|
-
|
|
501
|
-
- [ ]
|
|
502
|
-
- [ ]
|
|
503
|
-
- [ ]
|
|
504
|
-
- [ ]
|
|
300
|
+
- [ ] One `@Model` per entity, with identical names and types.
|
|
301
|
+
- [ ] Relationships have inverses on both sides.
|
|
302
|
+
- [ ] Non-trivial changes go through a `VersionedSchema` and a
|
|
303
|
+
`SchemaMigrationPlan`.
|
|
304
|
+
- [ ] The SwiftData configuration points at the existing SQLite file.
|
|
305
|
+
- [ ] The migration was tested on a copy of production data.
|
|
306
|
+
- [ ] Each entity has exactly one writer at any time.
|
|
307
|
+
- [ ] `automaticallyMergesChangesFromParent` is on in the Core Data stack.
|
|
308
|
+
- [ ] The `.xcdatamodeld` is deleted only after everything is verified.
|