SwiftData i iOS 26: Komplett Guide till @Model, CloudKit-synk och Migration från Core Data

Komplett guide till SwiftData i iOS 26: @Model, @Query, CloudKit-synk, relationer och migration från Core Data. Inkluderar kodexempel, prestandatips och felsökning.

SwiftData iOS 26: Komplett Guide 2026

Uppdaterad: 19 juni 2026

SwiftData är Apples deklarativa persistensramverk för Swift som låter dig modellera, lagra och fråga data i iOS 26, macOS 15 och senare med ren Swift-kod – ingen .xcdatamodeld-fil, ingen NSManagedObject-subklass och inget manuellt KVO. Du beskriver dina modeller med @Model-makrot, exponerar dem för vyer via @Query och bevarar dem i en ModelContainer som byggs ovanpå Core Data. Den här guiden visar hur SwiftData fungerar 2026, hur det skiljer sig från Core Data och hur du migrerar utan att förlora data.

  • SwiftData är ett typsäkert, Swift-native lager ovanpå Core Data. Med iOS 26 är det Apples rekommenderade default för all ny persistens i SwiftUI-appar.
  • @Model ersätter NSManagedObject och genererar automatiskt en schemamodell, identitet och Codable-konformitet vid kompilering.
  • @Query reagerar automatiskt på ändringar i ModelContext och uppdaterar vyer utan manuella NotificationCenter-prenumerationer.
  • iOS 26 lägger till indexerade properties, history tracking, sammansatta unika villkor och fullt #Predicate-stöd för relationer.
  • CloudKit-synkronisering aktiveras med en flagga på ModelConfiguration; alla properties måste ha defaultvärden och relationer måste vara valfria.
  • Migration från Core Data sker genom en VersionedSchema och en SchemaMigrationPlan. Datafiler kan återanvändas på plats om modellnamnen matchar.

Vad är SwiftData och varför ersätter det Core Data?

SwiftData är ramverket som Apple presenterade på WWDC23 och som nådde produktionsmognad med iOS 18. I iOS 26 är det det officiellt rekommenderade persistensramverket för alla nya SwiftUI-projekt. Tekniskt sett är SwiftData ingen ersättare för Core Data, utan ett deklarativt API som körs ovanpå samma SQLite-baserade persistenslager. Skillnaden är att du aldrig rör Core Data direkt. Du skriver vanliga Swift-klasser, dekorerar dem med @Model, och kompilatorn genererar resten: schema, identitetshantering, observation och Codable-konformitet.

Det här löser flera långvariga friktioner i Core Data. Du slipper synka tre källor till sanning (modellfil, genererad NSManagedObject-subklass, och Swift-kod), du får full statisk typkontroll på properties och predicates, och vyer reagerar automatiskt utan att du behöver hantera NSFetchedResultsController. I min senaste app halverades radmängden i datalagret jämfört med en jämförbar Core Data-version, och kompilatorn fångade fel som tidigare visade sig först vid runtime, till exempel felstavade nyckelvägar i predicates.

SwiftData är dessutom byggt för Swift-konkurrens från grunden. ModelContext är knuten till en aktör, och ModelActor-protokollet ger en typsäker väg att utföra tunga skrivningar utanför huvudtråden. Om du redan har omfamnat Approachable Concurrency i Swift 6.2 faller SwiftData in i samma mönster utan att du behöver tänka på trådar.

@Model, ModelContainer och ModelContext

Grundbygget i en SwiftData-app består av tre primitiver. @Model markerar en klass som en persistent modell, och makrot expanderar klassen till en observerbar, identifierbar typ med automatiskt genererad schemametadata. ModelContainer är behållaren som binder modellerna till en underliggande datafil (SQLite, in-memory eller CloudKit). ModelContext är det objekt du läser och skriver mot. Varje SwiftUI-vy får automatiskt en kontext via miljön.

import SwiftData
import SwiftUI

@Model
final class Recipe {
    @Attribute(.unique) var id: UUID
    var title: String
    var createdAt: Date
    var minutesToCook: Int
    @Relationship(deleteRule: .cascade, inverse: \Ingredient.recipe)
    var ingredients: [Ingredient] = []

    init(title: String, minutesToCook: Int) {
        self.id = UUID()
        self.title = title
        self.minutesToCook = minutesToCook
        self.createdAt = Date()
    }
}

@Model
final class Ingredient {
    var name: String
    var grams: Double
    var recipe: Recipe?

    init(name: String, grams: Double) {
        self.name = name
        self.grams = grams
    }
}

Containern monteras typiskt på App-roten med .modelContainer(for:). Du kan deklarera flera modeller i ett anrop, och iOS 26 låter dig dessutom skicka in en array av ModelConfiguration för att separera känslig data till sin egen krypterade fil.

@main
struct RecipeApp: App {
    var body: some Scene {
        WindowGroup {
            RecipeListView()
        }
        .modelContainer(for: [Recipe.self, Ingredient.self])
    }
}

Inuti en vy får du tag i kontexten via @Environment(\.modelContext). Anropa context.insert(_:) för att lägga till objekt. Ändringar sparas automatiskt vid nästa runloop-tick, men du kan tvinga fram en skrivning med try context.save() om du behöver garanterad persistens innan en bakgrundsuppgift körs.

@Query och #Predicate – reaktiv datahämtning

Hjärtat i SwiftUI-integrationen är property wrappern @Query. Den hämtar modeller från den aktiva kontexten och prenumererar automatiskt på ändringar. Sätter du in, uppdaterar eller raderar ett objekt så ritar SwiftUI om vyn utan att du skriver en rad observationskod. Det är samma reaktiva modell som @Observable och Observation-ramverket, fast på persistent data.

struct RecipeListView: View {
    @Query(
        filter: #Predicate<Recipe> { $0.minutesToCook <= 30 },
        sort: \Recipe.createdAt,
        order: .reverse
    ) private var quickRecipes: [Recipe]

    @Environment(\.modelContext) private var context

    var body: some View {
        NavigationStack {
            List(quickRecipes) { recipe in
                NavigationLink(value: recipe) {
                    Text(recipe.title)
                }
            }
            .navigationDestination(for: Recipe.self) { recipe in
                RecipeDetailView(recipe: recipe)
            }
            .toolbar {
                Button("Lägg till") {
                    context.insert(Recipe(title: "Nytt recept", minutesToCook: 20))
                }
            }
        }
    }
}

#Predicate-makrot är en av SwiftDatas viktigaste innovationer. Det tar ett Swift-uttryck, validerar det vid kompilering och översätter det till en SQL-fråga mot den underliggande lagringen. Du får full typkontroll, och ett felstavat fältnamn ger ett kompileringsfel istället för en runtime-krasch. I iOS 26 stöder predicates relationsnavigering ($0.ingredients.contains { $0.name == "Salt" }) och aggregerade funktioner som .count och .sum.

Behöver du dynamisk filtrering kan du bygga predicates programmatiskt eller använda FetchDescriptor direkt mot kontexten. Det är användbart i sökskärmar där användarens input bestämmer filtret. För djuplänkning och navigering kombineras detta naturligt med NavigationStack och deep linking.

Relationer, kaskadradering och unika villkor

SwiftData modellerar relationer som vanliga Swift-properties. Markera dem med @Relationship för att styra raderingsregler och invers. Tre raderingsregler stöds: .cascade raderar barnen när föräldern försvinner, .nullify nollställer referensen (default) och .deny blockerar borttagning om barn fortfarande existerar.

För att garantera dataintegritet finns två mekanismer. @Attribute(.unique) tvingar fram unikhet på enskilda properties, och i iOS 26 lägger Apple till stöd för sammansatta unika villkor via @Attribute(.unique, on: [\.field1, \.field2]). Försök till duplicering kastar ett SwiftDataError.uniqueConstraintViolation-fel vid save(). Honestly, det här är något jag glömde fånga i en tidig version av en app och det kraschade hela importflödet, så fånga felet och visa något användarvänligt istället.

@Model
final class User {
    @Attribute(.unique) var email: String
    var displayName: String
    @Relationship(deleteRule: .cascade) var posts: [Post] = []

    init(email: String, displayName: String) {
        self.email = email
        self.displayName = displayName
    }
}

Aktivera CloudKit-synkronisering i SwiftData

CloudKit-synkronisering aktiveras genom att skicka .private("iCloud.your.bundle.id") till ModelConfiguration. SwiftData sköter resten. Ändringar i lokal lagring pushas till iCloud och konflikter löses med last-writer-wins som default. För att detta ska fungera måste alla properties ha defaultvärden eller vara valfria, och alla relationer måste vara valfria. Det är CloudKits krav (inte SwiftDatas), men ramverket vägrar bygga schemat om reglerna bryts.

let config = ModelConfiguration(
    "RecipeStore",
    schema: Schema([Recipe.self, Ingredient.self]),
    cloudKitDatabase: .private("iCloud.com.example.recipes")
)
let container = try ModelContainer(
    for: Recipe.self, Ingredient.self,
    configurations: config
)

I iOS 26 har Apple lagt till stöd för delade CloudKit-databaser via .shared-konfiguration, vilket gör det möjligt att synka familjekonton eller team. Du bör också aktivera history tracking via ModelConfiguration(trackChanges: true) så att SwiftData kan beräkna deltan effektivt när enheter kommer online efter offline-tid. Glöm inte att aktivera Background Modes → Remote notifications i Xcodes capabilities så att push-uppdateringar når appen.

Migration från Core Data steg för steg

Att flytta en existerande Core Data-app till SwiftData kan ske utan datatab om du följer Apples migrationsväg. Eftersom SwiftData skriver till samma SQLite-format kan datafilen återanvändas på plats. Stegen är:

  1. Bevara filplats. Peka ut SwiftData-containern på samma URL som din nuvarande NSPersistentContainer via ModelConfiguration(url:).
  2. Spegla schemat. Skapa @Model-klasser med samma entitetsnamn och attributtyper som dina Core Data-entiteter. Namnen måste matcha exakt eftersom SwiftData använder klassnamnet som tabellnamn.
  3. Versionera schemat. Definiera en VersionedSchema per modellversion och en SchemaMigrationPlan som beskriver övergångarna.
  4. Kör appen mot en testdatabas för att verifiera att alla rader läses korrekt innan du distribuerar till produktion.
enum RecipeSchemaV1: VersionedSchema {
    static var versionIdentifier = Schema.Version(1, 0, 0)
    static var models: [any PersistentModel.Type] { [Recipe.self, Ingredient.self] }
}

enum RecipeMigrationPlan: SchemaMigrationPlan {
    static var schemas: [any VersionedSchema.Type] { [RecipeSchemaV1.self] }
    static var stages: [MigrationStage] { [] }
}

När du senare lägger till en property skapar du RecipeSchemaV2 och en MigrationStage (lättviktig om förändringen är additiv, custom om du behöver omvandla data). Apples officiella migrationsguide för Core Data till SwiftData innehåller den fullständiga checklistan inklusive hantering av tunga relationer och binär data.

Prestanda, indexering och felsökning

SwiftData är snabbt för de flesta use cases, men det finns några knep för stora datamängder. Indexera properties som ofta filtreras på med @Attribute(.index). Från iOS 26 är detta en first-class del av schemat och stödjer sammansatta index. Använd FetchDescriptor med fetchLimit och propertiesToFetch när du bara behöver en delmängd. Det minskar minnesfotavtrycket dramatiskt i listor med tusentals rader.

För skrivtunga operationer (importera CSV, synkronisera ett API) använd en bakgrundsaktör. ModelActor-protokollet ger dig en typsäker, trådkonform kontext att skriva till utan att blockera huvudtråden:

@ModelActor
actor RecipeImporter {
    func importRecipes(_ data: [RecipeDTO]) throws {
        for dto in data {
            let recipe = Recipe(title: dto.title, minutesToCook: dto.minutes)
            modelContext.insert(recipe)
        }
        try modelContext.save()
    }
}

För felsökning, aktivera SwiftDatas SQL-logg genom att sätta launch-argumentet -com.apple.CoreData.SQLDebug 1 i schemat. Du ser då exakt vilka SQL-queries som körs, vilket är ovärderligt för att hitta N+1-problem. Xcode 17:s nya Instruments-mall SwiftData Profiler visualiserar dessutom fetch-tider per query.

Om du arbetar med Apple Intelligence-funktioner och vill exponera SwiftData-objekt för Siri eller Spotlight kan du kombinera ramverket med App Intents. Då blir dina @Model-instanser indexerbara entiteter direkt mot systemets sökindex.

Vanliga frågor

Vad är skillnaden mellan SwiftData och Core Data?

SwiftData är ett deklarativt Swift-API ovanpå samma persistenslager som Core Data. Du skriver vanliga Swift-klasser med @Model istället för att underhålla en separat datamodellfil och NSManagedObject-subklasser. Core Data finns kvar och kan användas direkt om du behöver fin granulär kontroll.

Stöder SwiftData CloudKit-synkronisering?

Ja. Aktivera det genom att skicka cloudKitDatabase: .private("iCloud.your.bundle") till ModelConfiguration. Kom ihåg att alla properties måste ha defaultvärden och relationer måste vara valfria (det är CloudKits krav).

Kan jag använda SwiftData utan SwiftUI?

Ja, SwiftData är inte beroende av SwiftUI. Du kan skapa en ModelContainer manuellt och använda ModelContext från UIKit, AppKit eller en command-line-app. @Query är dock SwiftUI-specifik och måste ersättas med direkt FetchDescriptor-användning.

Hur kör jag SwiftData-skrivningar utanför huvudtråden?

Använd @ModelActor-protokollet. Det skapar en aktör med en egen ModelContext bunden till bakgrundstråden, vilket gör tunga importjobb och bulkskrivningar säkra utan att blockera UI:t.

Vilken iOS-version krävs för SwiftData?

SwiftData kräver iOS 17, macOS 14, watchOS 10 eller tvOS 17 som minimum. För indexerade properties, sammansatta unika villkor och delad CloudKit-synkronisering behöver du iOS 26 eller senare.

Editorial Team
Om Författaren Editorial Team

Our team of expert writers and editors.