Claude
Skills
Sign in
Back

swiftdata

Included with Lifetime
$97 forever

This skill should be used when working with SwiftData — @Model definitions, @Query, @Relationship, ModelContext, ModelContainer, schema migration, Swift 6 concurrency, performance optimization, or architecture review. Also triggers on: "SwiftData audit", "@Model issues", "SwiftData crashes", "predicate problems", "background context patterns", "N+1 queries", "SwiftData vs SQLiteData vs GRDB", "CloudKit sync with SwiftData", "SwiftData migration fails", or "SwiftData migration diagnostics". Covers auditing existing code, writing new SwiftData code, migration debugging, and architecture review.

Security

What this skill does


# SwiftData — Architecture, Patterns, and Audit Guide

Swift 6 strict concurrency only. No Swift 5 patterns.

## Decision: SwiftData vs SQLiteData vs GRDB

| Need | Choose | Why |
|------|--------|-----|
| Simple local CRUD + SwiftUI | **SwiftData** | Native integration, @Query, automatic observation |
| Complex queries, JOINs, FTS | **GRDB** | Full SQL access, 11 years battle-tested |
| SwiftData ergonomics + SQL power | **SQLiteData** | @Table structs, @FetchAll, CloudKit sharing support |
| Cross-platform (Android/Linux) | **GRDB** or raw SQLite | No Apple framework dependency |
| CloudKit shared databases | **SQLiteData** or Core Data | SwiftData only supports private CloudKit databases |

SwiftData is viable for production IF the patterns in this guide are followed. Most "SwiftData is broken" complaints trace to bad data modeling or missing abstractions, not framework bugs.

## Core Rules (Non-Negotiable)

These five rules prevent the most common crashes and data corruption. Violating any one is a critical-severity finding. For detailed code examples, consult `references/core-rules.md`.

### 1. @Model must be final class — never struct

`@Model struct` compiles but crashes at runtime or silently corrupts data. Always use `@Model final class` (omit `final` only for class inheritance on iOS 26+).

### 2. Never pass @Model objects across actor boundaries

`ModelContainer` and `PersistentIdentifier` are `Sendable`. Everything else is not. Pass `persistentModelID`, re-fetch on the target context.

### 3. Always use repository pattern with DTOs

The single architectural decision that determines success or failure with SwiftData. Views never touch `@Model` directly — repositories return `Sendable` DTOs, accept `PersistentIdentifier` for mutations. See `references/core-rules.md` for the full pattern.

### 4. Version your schema from the first release

Every @Model class MUST appear in its `VersionedSchema.models` array. Missing models are silently dropped during migration — permanent data loss. See `references/migration.md`.

### 5. Call save() explicitly for critical data

Main context autosaves on runloop idle. Background contexts have `autosaveEnabled = false`. On macOS, closing a window can lose unsaved main context changes. Always call `try context.save()` after mutations.

## Model Design Quick Reference

| Macro/Pattern | Purpose | Notes |
|---------------|---------|-------|
| `@Attribute(.unique)` | Upsert on collision | Primitives only |
| `@Attribute(.externalStorage)` | External file for large data | Mandatory for images/audio data |
| `@Attribute(originalName:)` | Rename property, preserve data | Enables lightweight migration |
| `@Transient` | Not persisted | Must have default value. Cannot appear in predicates |
| `#Unique<T>([\.a, \.b])` | Compound uniqueness | iOS 18+ |
| `#Index<T>([\.a], [\.b])` | Compound/single indexes | iOS 18+. Index predicate/sort properties only |
| `.cascade` delete rule | Delete parent deletes children | Use for owned relationships |
| `.nullify` delete rule | Sets child reference to nil | Default. Crashes if child property is non-optional |

For relationship initialization, enum workarounds, binary data patterns, and Codable struct hazards, consult `references/model-patterns.md`.

## Predicate Safety Summary

**Safe in #Predicate:** String operations (`localizedStandardContains`, `starts(with:)`), numeric/date comparisons, relationship traversal (`$0.album?.title`), Bool checks, nil checks, compound conditions.

**Compiles but CRASHES:** Enum properties (even RawRepresentable), `@Transient` properties, computed properties, generic predicates (Release builds only), optional to-many relationships, contents of value-type arrays (`[String]`), sub-properties of Codable structs.

For the full safe/crash pattern catalog and dynamic `@Query` patterns, consult `references/predicates-and-queries.md`.

## Concurrency (Swift 6 Strict)

Use `@ModelActor` for background work. Create with `Task.detached` to avoid running on main thread. Communicate across actors via `PersistentIdentifier` and `Sendable` DTOs only. For detailed patterns and gotchas, consult `references/concurrency.md`.

## Performance Essentials

- **N+1 prevention:** Use `relationshipKeyPathsForPrefetching` on FetchDescriptor
- **Batch operations:** Chunk inserts into batches of 500-1000, save each chunk
- **Indexing:** 2-3 indexes per model on queried/sorted properties. Each index slows writes
- **Prefer FetchDescriptor over @Query** for non-trivial work (fetchLimit, fetchOffset, propertiesToFetch, error handling)
- **Profiling:** `-com.apple.CoreData.SQLDebug 3` for EXPLAIN QUERY PLAN

For complete performance patterns, batch operation code, and profiling techniques, consult `references/performance.md`.

## Schema Migration

Lightweight migration handles: adding optional properties, removing properties, renaming with `@Attribute(originalName:)`, adding new models. Custom migration required for: type changes, non-optional without defaults, data transformations, deduplication.

Critical migration rules:
1. Data access goes in `willMigrate`, NOT `didMigrate`
2. Every @Model class must appear in its VersionedSchema.models array
3. Non-optional Codable properties block ALL future migrations (confirmed bug FB22151570)
4. Do NOT specify a SchemaMigrationPlan if implicit lightweight migration would suffice
5. Test migrations on real on-disk stores, not in-memory configurations

For complete migration patterns (including two-stage migration for type changes, many-to-many migration, junction table metadata, and deduplication), consult `references/migration.md`.

## Migration Diagnostics

When a migration fails, crashes, or loses data:

| Error Message | Likely Cause | Fix |
|---------------|-------------|-----|
| "Expected only Arrays for Relationships" | Many-to-many inverse missing | Add `@Relationship(inverse:)` |
| "incompatible model" / crash on launch | Schema version mismatch | Verify migration plan schemas array |
| "Failed to fulfill faulting for..." | Relationship integrity broken | Prefetch relationships during migration |
| Data gone after migration | Used didMigrate for old models | Move data access to willMigrate |
| Simulator works, device crashes | Simulator deletes DB on rebuild | Test on real device with real data |

For the complete diagnostic decision tree, step-by-step debugging protocol, verification checklists, and escalation procedures, consult `references/migration-diagnostics.md`.

## CloudKit Integration

SwiftData supports automatic CloudKit sync to private databases. All properties must be optional or have default values. All relationships must be optional. SwiftData uses last-write-wins conflict resolution by default. For full CloudKit patterns, constraints, sync status monitoring, and tvOS considerations, consult `references/cloudkit.md`.

## Audit Checklist

When reviewing SwiftData code, check in order of severity. Calculate risk: CRITICAL x 3 + HIGH x 2 + MEDIUM x 1 (cap at 10).

**CRITICAL (crash or data loss):** `@Model struct`, missing models in VersionedSchema, array relationship without `= []`, fetch in `didMigrate`, enum in `#Predicate`

**HIGH (data races, silent corruption):** Background Task using @Environment modelContext, missing save() in background contexts, updating both sides of inverse relationship, passing @Model across actor boundary, relationship assignment inside init()

**MEDIUM (performance):** N+1 relationship access in loops, 5+ indexes on one model, bulk insert without chunking, @Query with >1000 results, large Data without .externalStorage

For the full audit table with detection patterns, fixes, false positives, and risk scoring, consult `references/audit-checklist.md`.

## Known Bugs by iOS Version

- **iOS 17.0-17.1:** Avoid if possible. Enum predicate crashes, broken didSave/willSave, @ModelActor mutations don't trigger @Query updates
- **iOS 17.2+:** Minimum recommended target. Optional predicate ch

Related in Security