← All shorts
64iOS 17+2 min read

SwiftData: @Model and the Modern Persistence Stack

SwiftData replaces Core Data's NSManagedObject ceremony with a plain Swift @Model macro and a Swift-native container.

Core Data works, but wiring up an .xcdatamodeld file, generating NSManagedObject subclasses, and threading an NSPersistentContainer through the app is a lot of ceremony to store a handful of structs.

#The old way

import CoreData

final class Recipe: NSManagedObject {
    @NSManaged var title: String
    @NSManaged var servings: Int16
    @NSManaged var createdAt: Date
}

let container = NSPersistentContainer(name: "RecipeModel")
container.loadPersistentStores { _, error in
    if let error {
        fatalError("Unresolved error \(error)")
    }
}

let context = container.viewContext
let recipe = Recipe(context: context)
recipe.title = "Pancakes"
recipe.servings = 4
recipe.createdAt = .now
try? context.save()

#The new way

import SwiftUI
import SwiftData

@Model
final class Recipe {
    var title: String
    var servings: Int
    var createdAt: Date

    init(title: String, servings: Int, createdAt: Date = .now) {
        self.title = title
        self.servings = servings
        self.createdAt = createdAt
    }
}

@main
struct RecipeApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .modelContainer(for: Recipe.self)
    }
}

struct AddRecipeButton: View {
    @Environment(\.modelContext) private var context

    var body: some View {
        Button("Add Recipe") {
            context.insert(Recipe(title: "Pancakes", servings: 4))
        }
    }
}

#Why it matters

  • @Model generates the persistence machinery from a plain Swift class — no separate schema file to keep in sync
  • .modelContainer(for:) wires the container and injects modelContext into the environment in one call
  • Properties are ordinary vars: optionals, enums, and arrays work without NSManaged shims
  • Undo support, CloudKit sync, and migrations build on the same model definitions

#Gotcha

context.insert(_:) stages the object but doesn't write it to disk — SwiftData autosaves on a schedule (and on app background), but if a write needs to be durable immediately, call try context.save() yourself. Also, @Model classes are reference types with identity tied to the persistent store: comparing two fetched instances with == compares object identity, not field values, which surprises people coming from Core Data's similarly-shaped but differently-behaved managed objects. Check exact autosave timing against current docs before relying on it for a critical write path.

swiftdataiospersistence

Related shorts