SwiftData: The Ultimate Guide from iOS 17 to iOS 26

Published on Β· Updated Β· 39 min

Wlad
Wlad
Founder & Swift Tech Lead

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

AspectCore DataSwiftData

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:

DeleteRuleBehavior

.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 CaseRecommendation

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

YearSessionTopic

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

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.