references/cloudkit-sync.md
# CloudKit Sync
## Required Capabilities
SwiftData automatic sync requires:
- iCloud capability with CloudKit container,
- Background Modes with Remote notifications.
Without both, automatic server-driven updates are incomplete.
## Compatibility Constraints
CloudKit support is not universal across all SwiftData features.
- Review schema compatibility before enabling sync.
- Unique constraints and nonoptional relationships are documented limitations to account for.
- Plan schema carefully before production promotion.
Important production rule:
- CloudKit production schemas are additive-only after promotion.
## Container Selection
Default behavior:
- SwiftData reads entitlements and uses the first discovered container.
Explicit selection:
```swift
let config = ModelConfiguration(
cloudKitDatabase: .private("iCloud.com.example.MyApp")
)
```
Disable automatic SwiftData sync:
```swift
let config = ModelConfiguration(cloudKitDatabase: .none)
```
Use `.none` for apps that already use CloudKit with incompatible schema assumptions.
## Development Schema Initialization
For initialization workflows in development:
1. Build store description from SwiftData store URL.
2. Configure `NSPersistentCloudKitContainerOptions`.
3. Load store synchronously.
4. Initialize CloudKit schema.
5. Unload store before constructing SwiftData `ModelContainer`.
Run this workflow only in debug/nonproduction code paths.
## Verification Checklist
- CloudKit container visible and correct in Apple Developer configuration.
- Device receives background remote notifications.
- Development schema initialized and inspected in CloudKit Dashboard.
- Multi-device write/read scenarios validated before production rollout.
## Primary Documentation
- https://developer.apple.com/documentation/swiftdata/syncing-model-data-across-a-persons-devices
- https://developer.apple.com/documentation/swiftdata/modelconfiguration
- https://developer.apple.com/documentation/swiftdata/modelcontainer
references/concurrency-and-actors.md
# Concurrency and Actors
## Isolation Model
- Use `mainContext` for UI-bound operations.
- Use dedicated isolation for background persistence work.
- Avoid mixing long-running write flows directly in UI contexts.
## Model Actors
`@ModelActor` helps create actor-isolated persistence services with mutually exclusive access.
Benefits:
- serialized access to model operations,
- safer background processing,
- reduced accidental context sharing.
Pattern:
```swift
@ModelActor
actor TripStore {
func saveTrip(_ trip: Trip) throws {
modelContext.insert(trip)
try modelContext.save()
}
}
```
## Context Boundaries
- Do not pass mutable model instances loosely across isolation boundaries.
- Pass identifiers (`persistentModelID`) and refetch in the receiving context when needed.
- Keep context ownership explicit in service boundaries.
## Undo and Concurrency
- Automatic undo/redo integration is tied to main-context save flows.
- Background contexts are not a drop-in replacement for undo-enabled user editing.
## History with Concurrent Writers
- Set `modelContext.author` for different writers when useful.
- Filter fetched history by token and author to separate signal from noise.
## Primary Documentation
- https://developer.apple.com/documentation/swiftdata/concurrencysupport
- https://developer.apple.com/documentation/swiftdata/modelactor()
- https://developer.apple.com/documentation/swiftdata/modelactor
- https://developer.apple.com/documentation/swiftdata/modelexecutor
- https://developer.apple.com/documentation/swiftdata/defaultserialmodelexecutor
references/core-data-adoption.md
# Core Data Adoption
## Adoption Paths
Use one of three migration patterns:
1. Full conversion from Core Data to SwiftData.
2. Incremental migration by feature/module.
3. Coexistence (for example host app on Core Data, widget on SwiftData).
Choose based on release risk and integration constraints.
## Model Mapping Guidance
- Keep entity names, key attributes, and relationships aligned when migrating incrementally.
- Use `@Model` classes as the SwiftData model layer.
- Keep relationship and delete-rule semantics equivalent during migration.
## Coexistence Practices
When Core Data and SwiftData coexist:
- Use namespaced Core Data classes (`CDTrip`, etc.) to avoid class name collisions.
- Point both stacks to the same store URL when shared persistence is required.
- Enable persistent history tracking in the Core Data stack (`NSPersistentHistoryTrackingKey`) to match SwiftData expectations.
## Cross-Process Change Detection
For host app and widget workflows:
- Prefer consuming SwiftData persistent history over duplicate "unread" fields or side-channel storage.
- Track history token progress and process only relevant model updates.
## Migration Checklist
- Validate app group container and shared store path.
- Validate both stacks against same dataset.
- Validate deletes and relationship behavior across both stacks.
- Validate extension-driven updates in main app UI.
- Validate fallback behavior when history token expires.
## Primary Documentation
- https://developer.apple.com/documentation/coredata/adopting-swiftdata-for-a-core-data-app
- https://developer.apple.com/documentation/swiftdata/fetching-and-filtering-time-based-model-changes
references/implementation-playbooks.md
# Implementation Playbooks
## 1) Add a New Persisted Feature
1. Define or extend `@Model` classes.
2. Add relationship and delete-rule semantics explicitly where needed.
3. Add uniqueness and indexing strategy (if deployment target supports it).
4. Wire UI fetches through `@Query` or `FetchDescriptor`.
5. Validate CRUD and list behavior on realistic data volume.
6. Validate delete and rollback behavior.
Deliverables:
- model changes,
- query changes,
- migration impact statement.
## 2) Prepare a Schema Upgrade Release
1. Diff current and next schema in model code.
2. Classify changes as lightweight or custom migration candidates.
3. Introduce `VersionedSchema` and `SchemaMigrationPlan` when needed.
4. Rehearse migration on existing store snapshots.
5. Verify backward compatibility assumptions and failure behavior.
Deliverables:
- migration stage plan,
- rehearsal results,
- rollback and recovery notes.
## 3) Debug CloudKit Sync Divergence
1. Verify capabilities and remote notifications.
2. Confirm SwiftData container selection (`automatic`, explicit private, or `.none`).
3. Check schema compatibility constraints.
4. Validate writes on source device and reads on destination device.
5. Inspect history and context save flows for missed writes.
Deliverables:
- root-cause summary,
- config changes,
- validation evidence from at least two devices/simulators.
## 4) Handle Cross-Process Updates (Widget/Intent/App Extension)
1. Set context authoring strategy.
2. Fetch history using token + predicate.
3. Filter relevant changes by model type and changed attributes.
4. Update UI state and persist newest token.
5. Delete stale history safely after all consumers process it.
Deliverables:
- token persistence path,
- history filtering logic,
- cleanup policy.
## 5) Improve Query Performance
1. Identify slow user-visible queries.
2. Align predicates and sort descriptors with indexes.
3. Add fetch limits, offsets, or identifier-only fetches.
4. Eliminate duplicate filtering logic in view code.
5. Compare behavior before and after changes on large datasets.
Deliverables:
- before/after query strategy,
- measured or observed UX impact,
- remaining risks.
## Primary Documentation
- https://developer.apple.com/documentation/swiftdata/preserving-your-apps-model-data-across-launches
- https://developer.apple.com/documentation/swiftdata/filtering-and-sorting-persistent-data
- https://developer.apple.com/documentation/swiftdata/schemamigrationplan
- https://developer.apple.com/documentation/swiftdata/syncing-model-data-across-a-persons-devices
- https://developer.apple.com/documentation/swiftdata/fetching-and-filtering-time-based-model-changes
references/migrations-and-history.md
# Migrations and History
## Schema Evolution Strategy
1. Start with automatic (lightweight) migration expectations.
2. If changes exceed lightweight capabilities, define `SchemaMigrationPlan`.
3. Model versions explicitly with `VersionedSchema`.
4. Use `MigrationStage.lightweight(...)` or `MigrationStage.custom(...)` between versions.
Use `originalName` and, when needed, `hashModifier` to preserve continuity for renamed properties.
## Migration Plan Skeleton
```swift
enum AppMigrationPlan: SchemaMigrationPlan {
static var schemas: [any VersionedSchema.Type] {
[SchemaV1.self, SchemaV2.self]
}
static var stages: [MigrationStage] {
[.lightweight(fromVersion: SchemaV1.self, toVersion: SchemaV2.self)]
}
}
```
## Persistent History Usage
Use history when you need cross-process or temporal change tracking (widgets, intents, extensions, background writers).
- Fetch by token and/or author with `HistoryDescriptor`.
- Store latest token after successful processing.
- Filter transaction changes to only relevant model types and attributes.
- Delete stale transactions to reclaim disk.
## Deletion Tombstones
If deleted models must remain externally identifiable:
- mark key fields with `@Attribute(.preserveValueOnDeletion)`,
- read preserved values from delete change tombstones.
## Operational Risks
- `historyTokenExpired` means requested history was already deleted.
- Rebuild token baseline after cleanup or retention-window changes.
- Ensure cleanup strategy does not delete history before all consumers process it.
## Release Notes to Track
- 2024 updates: `#Unique`, `#Index`, history APIs, custom data store protocols.
- 2025 updates: inheritance support and history sorting improvements.
## Primary Documentation
- https://developer.apple.com/documentation/swiftdata/schemamigrationplan
- https://developer.apple.com/documentation/swiftdata/versionedschema
- https://developer.apple.com/documentation/swiftdata/migrationstage
- https://developer.apple.com/documentation/swiftdata/fetching-and-filtering-time-based-model-changes
- https://developer.apple.com/documentation/swiftdata/historydescriptor
- https://developer.apple.com/documentation/updates/swiftdata
references/model-context-and-lifecycle.md
# ModelContext and Lifecycle
## Container Setup First
- Attach `.modelContainer(for: ...)` at app, scene, or top-level view.
- Or create `ModelContainer(...)` manually and inject it.
- If no container is attached, environment context is in-memory and schema-less:
- inserts throw,
- fetches return empty.
## Context Roles
- `container.mainContext` (or `@Environment(\.modelContext)`) is main-actor-bound and intended for UI-driven work.
- Custom `ModelContext(container)` is useful for controlled background or utility work.
## Autosave and Explicit Save
- `mainContext` is configured with autosave enabled by SwiftData.
- Manually created contexts are not implicitly configured the same way; set `autosaveEnabled` if needed.
- Use explicit `try context.save()` when operation boundaries must be deterministic.
- Use `transaction { ... }` for grouped mutations followed by save.
## Insert, Update, Delete
- Insert only graph roots; SwiftData traverses related models automatically.
- Updates are tracked automatically for known models; no explicit update API.
- `delete(_:)` removes specific instances.
- `delete(model:where:includeSubclasses:)` can remove many models at once.
- Warning: no predicate means deleting all models of that type.
## Undo and Notifications
- Enable undo with `.modelContainer(..., isUndoEnabled: true)`.
- Automatic undo/redo support applies to changes saved through `mainContext`.
- Observe `ModelContext.willSave` and `ModelContext.didSave` for lifecycle hooks.
- Always scope notification subscriptions to a specific context object.
## Selection and Identity
- Use `persistentModelID` for stable selection identity in UI.
- Clear selection before deleting the selected object to avoid stale references.
## Safe Operational Pattern
```swift
@Environment(\.modelContext) private var context
func removeExpiredTrips() {
do {
try context.delete(model: Trip.self, where: #Predicate { $0.endDate < .now })
try context.save()
} catch {
// Report and recover.
}
}
```
## Primary Documentation
- https://developer.apple.com/documentation/swiftdata/modelcontainer
- https://developer.apple.com/documentation/swiftdata/modelcontext
- https://developer.apple.com/documentation/swiftdata/modelcontext/autosaveenabled
- https://developer.apple.com/documentation/swiftdata/modelcontext/delete(model:where:includesubclasses:)
- https://developer.apple.com/documentation/swiftdata/deleting-persistent-data-from-your-app
- https://developer.apple.com/documentation/swiftdata/reverting-data-changes-using-the-undo-manager
references/modeling-and-schema.md
# Modeling and Schema
## Core Rules
- Annotate persistable classes with `@Model`.
- Treat model code as the schema source of truth.
- Expect noncomputed stored properties to persist by default when types are supported.
- Use primitive types and `Codable` value types for persisted attributes.
- Remember computed properties are effectively transient.
## Attribute Design
Use `@Attribute(...)` to override default behavior when needed:
- `.unique`: enforce uniqueness for a single attribute.
- `.preserveValueOnDeletion`: keep selected values in history tombstones after delete.
- `.spotlight`, `.allowsCloudEncryption`, `.externalStorage`: use only when product requirements justify them.
- `originalName`: map renamed properties for migration continuity.
- `hashModifier`: advanced schema hashing override for migration scenarios.
Prefer explicit annotations only where behavior differs from defaults.
## Unique and Index Macros (iOS 18+)
For iOS 18+ targets, prefer freestanding macros at model scope:
- `#Unique<Model>([\.id], [\.name, \.date])` for single or compound uniqueness constraints.
- `#Index<Model>([\.date], [\.status, \.date])` for query-oriented binary indexes.
- `#Index<Model>(...)` with typed index variants for advanced indexing modes.
Notes:
- `#Unique` supports to-one relationship attributes, not arrays of related models.
- Keep index definitions aligned with real query predicates and sort keys.
## Relationships as Schema
- Use `@Relationship(...)` when data is dynamic and belongs to another model.
- Use enums (`Codable`) when related data is static and app-defined.
- Set `inverse` explicitly when clarity matters.
- Treat delete rules as domain rules, not implementation details.
## Transient Data
Use `@Transient` for runtime-only state that must not be stored.
- For nonoptional transient properties, provide a default value.
- Keep network/loading/UI flags transient.
## Schema Availability and Planning
- Base SwiftData model macros are available from iOS 17.
- `#Unique` and `#Index` are available from iOS 18.
- Inheritance support appears in newer updates and examples (check deployment targets before adopting in shared code paths).
## Example Pattern
```swift
@Model
final class Trip {
#Unique<Trip>([\.externalID])
#Index<Trip>([\.startDate], [\.destination, \.startDate])
@Attribute(.unique) var externalID: String
var destination: String
var startDate: Date
@Relationship(deleteRule: .cascade, inverse: \Activity.trip)
var activities: [Activity] = []
@Transient var isExpanded = false
}
```
## Primary Documentation
- https://developer.apple.com/documentation/swiftdata/model()
- https://developer.apple.com/documentation/swiftdata/attribute(_:originalname:hashmodifier:)
- https://developer.apple.com/documentation/swiftdata/unique(_:)
- https://developer.apple.com/documentation/swiftdata/index(_:)-74ia2
- https://developer.apple.com/documentation/swiftdata/index(_:)-7d4z0
- https://developer.apple.com/documentation/swiftdata/transient()
references/querying-and-fetching.md
# Querying and Fetching
## Choosing the API
- Use `@Query` in SwiftUI views for automatic refresh and simple binding to UI.
- Use `FetchDescriptor` + `modelContext.fetch(...)` outside views or for explicit control.
- Use `fetchCount(...)` when only count is needed.
- Use `fetchIdentifiers(...)` when only IDs are needed.
## Deterministic Query Design
- Centralize predicate construction in helper functions.
- Reuse the same predicate across related views (for example list + map) to prevent mismatch.
- Always define sort order explicitly for user-visible lists.
- Keep dynamic query parameters in the view initializer to force predictable query rebuilds.
## Dynamic Query Pattern
```swift
init(searchText: String, date: Date) {
let predicate = Quake.predicate(searchText: searchText, searchDate: date)
_quakes = Query(filter: predicate, sort: \.magnitude, order: .reverse)
}
```
## FetchDescriptor Controls
Configure `FetchDescriptor<T>` with:
- `predicate`: filter criteria.
- `sortBy`: one or more sort descriptors.
- `fetchLimit`: cap result size.
- `fetchOffset`: pagination offset.
- `includePendingChanges`: include unsaved changes in matching.
- `relationshipKeyPathsForPrefetching`: reduce relationship faulting overhead.
- `propertiesToFetch`: select only needed properties.
## Performance Guidance
- Combine indexing strategy with actual query keys.
- Avoid broad unbounded queries for high-cardinality models.
- Prefer count or identifier fetches for preliminary checks.
- Use explicit fetch limits for user-facing screens.
- Avoid repeated ad hoc filtering in `body`; encode filtering in query predicate.
## Common Failures
- `unsupportedPredicate` or `unsupportedSortDescriptor`: simplify predicate/sort to supported expressions.
- Inconsistent UI between views: shared predicate was not reused.
- Slow list rendering: missing indexes for frequently used sort/filter attributes.
## Primary Documentation
- https://developer.apple.com/documentation/swiftdata/query
- https://developer.apple.com/documentation/swiftdata/query()
- https://developer.apple.com/documentation/swiftdata/additionalquerymacros
- https://developer.apple.com/documentation/swiftdata/fetchdescriptor
- https://developer.apple.com/documentation/swiftdata/filtering-and-sorting-persistent-data
references/relationships-and-inheritance.md
# Relationships and Inheritance
## Relationship Strategy
- Use enums (`Codable`) for static, app-defined classifications.
- Use model-to-model relationships for dynamic data created by users or external systems.
## `@Relationship` Essentials
Key parameters:
- `deleteRule`: behavior on owner deletion (`nullify`, `cascade`, `deny`, `noAction`).
- `inverse`: inverse key path to maintain object graph consistency.
- `minimumModelCount` and `maximumModelCount`: optional cardinality constraints.
- `originalName`: migration mapping support for renamed relationships.
Default delete rule is `.nullify`.
Important detail:
- If a relationship property is optional, min/max enforcement applies only when the property is non-`nil`.
## Delete Rule Guidance
- Use `.cascade` when related data has no standalone value.
- Use `.nullify` when related data can outlive the parent.
- Use `.deny` when parent deletion must be blocked while dependents exist.
- Validate delete behavior with tests before release.
## Inheritance Guidance
Use inheritance when there is a strong IS-A model:
- `BusinessTrip` is a `Trip`.
- `PersonalTrip` is a `Trip`.
Avoid inheritance when:
- specialization is too minor and better represented by a field/enum;
- query model is purely shallow and would only target subclasses with duplicated parent fields.
Inheritance tends to fit mixed deep + shallow querying requirements.
## Querying Across Hierarchies
- Base-class query for broad search across shared fields.
- Type-filtered predicates for subtype-only views:
- `#Predicate { $0 is BusinessTrip }`
- `#Predicate { $0 is PersonalTrip }`
## Primary Documentation
- https://developer.apple.com/documentation/swiftdata/defining-data-relationships-with-enumerations-and-model-classes
- https://developer.apple.com/documentation/swiftdata/relationship(_:deleterule:minimummodelcount:maximummodelcount:originalname:inverse:hashmodifier:)
- https://developer.apple.com/documentation/swiftdata/schema/relationship/deleterule-swift.enum
- https://developer.apple.com/documentation/swiftdata/adopting-inheritance-in-swiftdata
references/troubleshooting-and-updates.md
# Troubleshooting and Updates
## Frequent Failure Modes
- `missingModelContext`: no valid container wiring for current execution path.
- `modelValidationFailure`: schema/model constraints are violated at save time.
- `unsupportedPredicate` / `unsupportedSortDescriptor`: expression is not supported for store-side evaluation.
- `includePendingChangesWithBatchSize`: invalid fetch configuration combination.
- `historyTokenExpired`: history token points to pruned transactions.
- `unknownSchema` / `backwardMigration`: migration path is invalid or unsupported.
## Practical Debug Sequence
1. Confirm container and schema setup.
2. Confirm deployment target supports the APIs in use.
3. Reproduce with a minimal `FetchDescriptor` and no optional filters.
4. Validate delete predicates and save boundaries.
5. Validate history token lifecycle (load, use, persist, cleanup).
6. Validate CloudKit mode (`automatic`, explicit container, or `.none`).
## API Availability Snapshot
- SwiftData base APIs (`@Model`, `ModelContainer`, `ModelContext`, `Query`): iOS 17+.
- Persistent history descriptor and many history/data-store APIs: iOS 18+.
- `#Unique` and `#Index` macros: iOS 18+.
- Inheritance support is highlighted in June 2025 updates and iOS 26-era docs; always gate by deployment target.
## Release-Aware Recommendations
When advising changes:
- avoid recommending `#Unique` or `#Index` on iOS 17-only apps;
- avoid relying on newer history sort features unless iOS 26-era toolchains are present;
- provide fallback plans for older deployment targets.
## Primary Documentation
- https://developer.apple.com/documentation/swiftdata/swiftdataerror
- https://developer.apple.com/documentation/swiftdata/datastoreerror
- https://developer.apple.com/documentation/updates/swiftdata
SKILL.md
---
name: swiftdata-expert-skill
description: Expert guidance for designing, implementing, migrating, and debugging SwiftData persistence in Swift and SwiftUI apps. Use when working with @Model schemas, @Relationship/@Attribute rules, Query or FetchDescriptor data access, ModelContainer/ModelContext configuration, CloudKit sync, SchemaMigrationPlan/history APIs, ModelActor concurrency isolation, or Core Data to SwiftData adoption/coexistence.
---
# SwiftData Expert Skill
## Overview
Use this skill to build, review, and harden SwiftData persistence architecture with Apple-documented patterns from iOS 17 through current updates. Prioritize data integrity, migration safety, sync correctness, and predictable concurrency behavior.
## Agent Behavior Contract (Follow These Rules)
1. Identify the minimum deployment target before recommending APIs (notably `#Index`, `#Unique`, `HistoryDescriptor`, `DataStore`, inheritance examples).
2. Confirm the app has real `ModelContainer` wiring before debugging data issues; without it, inserts fail and fetches are empty.
3. Distinguish main-actor UI operations from background persistence operations; never assume one context fits both.
4. Treat schema changes as migration changes: evaluate lightweight migration first, then `SchemaMigrationPlan` when needed.
5. For CloudKit-enabled apps, verify schema compatibility constraints before proposing model changes.
6. Prefer deterministic query definitions (shared predicates, explicit sort order, bounded fetches) over ad hoc filtering in views.
7. Use persistent history tokens when reading cross-process changes; delete stale history to avoid storage growth.
8. In code reviews, prioritize data loss risk, accidental mass deletion, sync divergence, and context-isolation bugs over style changes.
## Analysis Commands (Use Early)
- Search container setup:
- `rg "modelContainer\\(|ModelContainer\\(" -n`
- Search model definitions:
- `rg "^@Model|#Unique|#Index|@Relationship|@Attribute|@Transient" -n`
- Search context usage:
- `rg "modelContext|mainContext|ModelContext\\(" -n`
- Search migrations and history:
- `rg "SchemaMigrationPlan|VersionedSchema|MigrationStage|fetchHistory|deleteHistory|historyToken" -n`
- Search CloudKit and app groups:
- `rg "cloudKitDatabase|iCloud|CloudKit|groupContainer|AppGroup|NSPersistentCloudKitContainer" -n`
## Project Intake (Before Advising)
- Determine deployment targets: iOS, iPadOS, macOS, watchOS, and visionOS.
- Locate container setup: `.modelContainer(...)` modifier or manual `ModelContainer(...)`.
- Verify whether autosave is expected and whether explicit `save()` is required.
- Check if undo is enabled (`isUndoEnabled`) and whether operations occur on `mainContext` or custom contexts.
- Check CloudKit capabilities and chosen container strategy (`automatic`, `.private(...)`, `.none`).
- Check if app group storage is required.
- Check if Core Data coexistence is in scope.
- Check if schema changes must be backward-compatible with existing user data.
## Workflow Decision Tree
1. Need a new model or schema shape:
- Read `references/modeling-and-schema.md`.
2. Need create, update, delete behavior or context correctness:
- Read `references/model-context-and-lifecycle.md`.
3. Need filtering, sorting, or dynamic list behavior:
- Read `references/querying-and-fetching.md`.
4. Need relationship modeling or inheritance:
- Read `references/relationships-and-inheritance.md`.
5. Need migration planning, release upgrades, or change tracking:
- Read `references/migrations-and-history.md`.
6. Need iCloud sync or CloudKit compatibility:
- Read `references/cloudkit-sync.md`.
7. Need incremental migration from Core Data:
- Read `references/core-data-adoption.md`.
8. Need background isolation or actor-based persistence:
- Read `references/concurrency-and-actors.md`.
9. Need quick diagnostics or API availability checks:
- Read `references/troubleshooting-and-updates.md`.
10. Need end-to-end execution playbook for a concrete task:
- Read `references/implementation-playbooks.md`.
## Triage-First Playbook (Common Problems -> Next Move)
- Insert fails or fetch is always empty:
- Confirm `.modelContainer(...)` is attached at app or window root and the model type is included.
- Duplicate rows appear after network refresh:
- Add `@Attribute(.unique)` or `#Unique` constraints and rely on insert-upsert behavior.
- Unexpected data loss during delete:
- Audit delete rules (`.cascade` vs `.nullify`) and check for unbounded `delete(model:where:)`.
- Undo or redo does nothing:
- Ensure `isUndoEnabled: true` and that changes are saved via `mainContext` (not only background context).
- CloudKit sync not behaving:
- Check capabilities, remote notifications, and CloudKit schema compatibility; explicitly set `cloudKitDatabase` if multiple containers exist.
- Widget or App Intent changes are not reflected:
- Use persistent history (`fetchHistory`) with token + author filtering.
- `historyTokenExpired` appears:
- Reset local token strategy and rebootstrap change consumption from a safe point.
- Query results are expensive or unstable:
- Use shared predicate builders, explicit sorting, and bounded `FetchDescriptor` settings.
## Anti-Patterns (Reject by Default)
- Building persistence logic before validating container wiring.
- Performing broad deletes without predicate review and confirmation.
- Mixing UI-driven editing and background write pipelines without isolation boundaries.
- Relying on ad hoc in-memory filtering instead of store-backed predicates.
- Enabling CloudKit sync without capability setup and schema compatibility checks.
- Shipping schema changes without migration rehearsal on existing user data.
- Consuming history without token persistence and cleanup policy.
## Core Patterns
### App-level container wiring (SwiftUI)
```swift
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
RootView()
}
.modelContainer(for: [Trip.self, Accommodation.self])
}
}
```
### Manual container configuration
```swift
let config = ModelConfiguration(isStoredInMemoryOnly: false)
let container = try ModelContainer(
for: Trip.self,
Accommodation.self,
configurations: config
)
```
### Dynamic query setup in a view initializer
```swift
struct TripListView: View {
@Query private var trips: [Trip]
init(searchText: String) {
let predicate = #Predicate<Trip> {
searchText.isEmpty || $0.name.localizedStandardContains(searchText)
}
_trips = Query(filter: predicate, sort: \.startDate, order: .forward)
}
var body: some View { List(trips) { Text($0.name) } }
}
```
### Safe batch delete pattern
```swift
do {
try modelContext.delete(
model: Trip.self,
where: #Predicate { $0.endDate < .now },
includeSubclasses: true
)
try modelContext.save()
} catch {
// Handle delete and save failures.
}
```
## Reference Files
- `references/modeling-and-schema.md`
- `references/model-context-and-lifecycle.md`
- `references/querying-and-fetching.md`
- `references/relationships-and-inheritance.md`
- `references/migrations-and-history.md`
- `references/cloudkit-sync.md`
- `references/core-data-adoption.md`
- `references/concurrency-and-actors.md`
- `references/troubleshooting-and-updates.md`
- `references/implementation-playbooks.md`
## Best Practices Summary
1. Keep model code as the source of truth; avoid hidden schema assumptions.
2. Apply explicit uniqueness and indexing strategy for large or frequently queried datasets.
3. Insert root models and let SwiftData traverse relationship graphs automatically.
4. Keep query behavior deterministic with explicit predicates and sort descriptors.
5. Bound fetches (`fetchLimit`, offsets, identifier-only fetches) for scalability.
6. Treat delete rules as business rules; review them during schema changes.
7. Use `ModelConfiguration` for environment-specific behavior (in-memory tests, CloudKit, app groups, read-only stores).
8. Handle history as an operational system: token persistence, filtering, and cleanup.
9. Use model actors or isolated contexts for non-UI persistence work.
10. Gate recommendations by API availability and deployment target.
## Verification Checklist (After Changes)
- Build succeeds for target platforms and minimum deployment versions.
- CRUD tests pass with real store and in-memory store.
- Relationship deletes behave as intended (`cascade`, `nullify`, and others).
- Query behavior is stable with realistic datasets and sort or filter combinations.
- Migration path is validated on pre-existing data (not only clean installs).
- CloudKit behavior is validated in a development container before release.
- Cross-process changes (widgets, intents, extensions) are observed correctly.
- Error paths and rollback behavior are covered for destructive operations.
## Response Contract
- For review tasks, report findings first by severity and include exact file paths and lines.
- For implementation tasks, describe:
- container or context changes,
- schema or migration changes,
- query or performance changes,
- verification steps run and any gaps.
- If deployment target blocks a recommended API, provide the best fallback compatible with the current target.