SwiftData: The Ultimate Guide from iOS 17 to iOS 26
Published on Β· Updated Β· 39 min
What is SwiftData?
SwiftData is a declarative persistence framework that allows you to model and store your application's data in pure Swift. It uses Swift macros to transform ordinary classes into persistent models, eliminating the need for .xcdatamodeld files and code generation.
Key features:
Pure Swift modeling with the @Model macro
Native SwiftUI integration via @Query
Automatic CloudKit synchronization
Simplified schema migrations
Modern concurrency with @ModelActor
Multi-platform support (iOS, macOS, watchOS, tvOS, visionOS)
SwiftData is not a complete replacement for Core Data, but rather a new API built on its foundations. The default storage format remains compatible with Core Data.
SwiftData vs Core Data
| Aspect | Core Data | SwiftData |
|---|---|---|
Modeling | .xcdatamodeld file | Swift code (@Model) |
Queries | NSFetchRequest | @Query, FetchDescriptor |
Context | NSManagedObjectContext | ModelContext |
Concurrency | Manual queues | Native @ModelActor |
SwiftUI | Wrappers required | Native integration |
Minimum iOS | iOS 3+ | iOS 17+ |
Maturity | 20 years, very stable | Rapid evolution |
Features | Complete | Catching up |
For new projects targeting iOS 17+, SwiftData is recommended. For existing projects or those requiring advanced features (batch operations, faulting), Core Data remains relevant.
Evolution Timeline
iOS 17 β September 2023
Initial launch with fundamentals:
@Model macro for modeling
@Query for reactive queries
ModelContainer and ModelContext
Basic CloudKit synchronization
VersionedSchema for migrations
iOS 18 β September 2024
Major architecture evolution:
#Unique: composite constraints with upsert
#Index: query optimization
#Expression: calculations in predicates
History API: change tracking
Custom DataStore: custom backends
preserveValueOnDeletion: tombstones
iOS 26 β June 2025
Stabilization and new features:
Model inheritance (class inheritance)
Critical bug fixes (@ModelActor, Codable predicates)
Better Swift 6.2 integration
History sortBy for optimized pagination
Getting Started with SwiftData
The @Model Macro
Transforming a class into a persistent model requires just one line:
The @Model macro automatically generates conformance to PersistentModel and Observable, allowing SwiftUI to react to changes.
ModelContainer and ModelContext
ModelContainer manages persistent storage, while ModelContext represents the workspace for CRUD operations:
The .modelContainer modifier automatically configures the container and injects the context into the SwiftUI environment.
Advanced Container Configuration
For more control, create the container manually:
@Query: Reactive Queries
The @Query macro transforms queries into reactive properties:
@Query parameters allow sorting, filtering, and limiting:
CRUD Operations
Basic operations are performed through ModelContext:
Data Modeling
Supported Types
SwiftData natively supports:
Primitives: String, Int, Double, Bool, Date, Data, UUID, URL
Collections: Array, Dictionary, Set
Optionals: Type?
Enums: with RawValue or Codable
Custom Codable types
@Attribute: Property Customization
The @Attribute macro allows configuring persistence behavior:
@Relationship: Relations Between Models
SwiftData supports one-to-one, one-to-many, and many-to-many relationships:
Available delete rules:
| DeleteRule | Behavior |
|---|---|
.nullify | Sets the relation to nil (default) |
.cascade | Deletes related objects |
.deny | Blocks if objects are related |
.noAction | No automatic action |
@Transient: Non-Persisted Properties
For computed or temporary properties:
Queries and Filtering
FetchDescriptor: Programmatic Queries
For queries outside of SwiftUI, use FetchDescriptor:
#Predicate: Type-Safe Filtering
The #Predicate macro offers compiled and type-safe filtering:
Compound Predicates
Combine multiple conditions with logical operators:
#Expression: Calculations in Queries (iOS 18)
The #Expression macro allows performing calculations directly in queries:
Constraints and Optimization (iOS 18)
#Unique: Composite Constraints
The #Unique macro guarantees uniqueness and performs automatic upserts:
When inserting a Trip with the same name/startDate/endDate, SwiftData performs an update instead of creating a duplicate.
#Index: Query Acceleration
Create indexes on frequently filtered or sorted properties:
preserveValueOnDeletion: Tombstones
To preserve values after deletion (useful for History API):
Schema Migrations
VersionedSchema: Versioning Your Models
Encapsulate each version of your schema:
SchemaMigrationPlan: Migration Plan
Define the migration order and custom steps:
Custom Migrations
For complex transformations:
From Unversioned to Versioned
If your app is already in production without VersionedSchema:
CloudKit Sync
Automatic Configuration
Enable iCloud in your project and configure the container:
CloudKit database options:
.automatic: Automatic sync with the default container
.private("container.id"): Specific private container
.none: No CloudKit sync
Important Limitations
CloudKit with SwiftData has constraints:
.unique is NOT supported with CloudKit (generates duplicates)
No sync of empty optional relationships
No shared/public databases support
Conflicts resolved by "last write wins"
Debug CloudKit
Enable logs for diagnostics:
Concurrency and @ModelActor
The Problem: @Query Blocks MainActor
@Query and ModelContext run on the MainActor. For heavy operations, this blocks the UI:
@ModelActor: Background Operations
Create a dedicated actor for heavy operations:
Usage from SwiftUI
PersistentIdentifier: Passing Between Actors
Models are not Sendable. Use their identifiers:
History Tracking (iOS 18)
Enabling History Tracking
History is enabled by default with DefaultStore. Use preserveValueOnDeletion to retain identifiers of deleted items:
Retrieving Change History
Use Case: Server Synchronization
Custom DataStore (iOS 18)
Key Concepts
iOS 18 allows replacing the SQLite backend with any storage system via the DataStore protocol:
DataStoreConfiguration: describes the store
DataStoreSnapshot: represents model values
DataStore: implements fetch/save
Creating a JSONStore
Simplified example of a JSON-based store:
Model Inheritance (iOS 26)
When to Use Inheritance
Inheritance is appropriate when:
Models form a natural hierarchy (is-a relationship)
They share common properties
You want polymorphic queries
Polymorphic Queries
Use the is keyword in predicates:
Performance: Beware of Single Table Inheritance
SwiftData stores all subtypes in a single table (Wide Table). This can impact performance with many different subclasses, very different properties between subclasses, or large data volumes.
For complex hierarchies, prefer composition with relationships.
Advanced Debugging
Core Data Launch Arguments
Add these arguments in your scheme (Edit Scheme β Arguments):
SQLite Database Inspection
Locate and inspect the data file:
Instruments: Core Data Template
- Product β Profile (Command+I)
- Choose "Core Data"
- Available instruments:
Core Data Fetches: fetch duration and frequency
Core Data Saves: save operations
Core Data Faults: lazy loading
Core Data Cache Misses: uncached accesses
Best Practices
Recommended Architecture: Repository Pattern
Isolate SwiftData behind an abstraction:
Unit Tests with In-Memory Store
Xcode Previews
Create a dedicated preview container:
Common Pitfalls and Solutions
@ModelActor View Updates Bug (Fixed in iOS 26)
Before iOS 26, modifications via @ModelActor didn't always trigger SwiftUI updates:
iOS 26 fixes this bug with backward compatibility to iOS 17.
Codable in Predicates (Fixed in iOS 26)
Before iOS 26, using Codable properties in predicates didn't work:
CloudKit + .unique Incompatibility
Cascade Deletion Not Working
Known bug: .cascade doesn't always delete children:
Alternatives and Interop
When to Choose an Alternative
| Use Case | Recommendation |
|---|---|
New simple iOS 17+ project | SwiftData |
Existing Core Data project | Stay on Core Data |
Custom server sync | GRDB or Realm |
Shared cross-platform database | SQLite (GRDB) |
Critical performance | Core Data or GRDB |
Advanced features (batch, faulting) | Core Data |
Popular Alternatives
GRDB: Powerful SQLite wrapper, very performant
Realm: Complete solution with sync, cross-platform
Blackbird: SwiftUI-first, simple and lightweight
SwiftData + Core Data Cohabitation
Possible but complex. Identifiers are not compatible. Recommended approach: progressive migration β keep Core Data for critical features, new models in SwiftData, migrate progressively as SwiftData matures.
Official Resources
Apple Documentation
WWDC Sessions
| Year | Session | Topic |
|---|---|---|
2023 | Meet SwiftData | Introduction |
2023 | Build an app with SwiftData | Hands-on tutorial |
2023 | Model your schema with SwiftData | Modeling |
2024 | What's new in SwiftData | #Unique, #Index, History |
2024 | Create a custom data store | DataStore protocol |
2024 | Track model changes with history | History API |
2025 | Dive into inheritance and migration | iOS 26 inheritance |
Recommended Blogs
FatBobMan (Xu Yang) β Deep technical analysis
Donny Wals β Practical tutorials
Use Your Loaf β Concise references
SwiftData represents the future of persistence on Apple platforms. Despite its youth which translates to some limitations, the framework evolves rapidly and becomes increasingly viable for production applications. By mastering the concepts in this guide, you're ready to confidently adopt it in your projects.