Featured blog image
IOS 10 min read

📝 SwiftData — 4/30 Days ModelContainer & ModelContext (Lifecycle & Concurrency)

Author

Arjun

IOS Developer

📝 SwiftData — ModelContainer & ModelContext (Lifecycle & Concurrency)

📦 ModelContainer

  • A container that manages your model schema and storage (backed by SQLite by default)

  • Created once (usually in App struct)

  • Can have multiple ModelContexts`

    let container = try ModelContainer(for: [User.self, Task.self])


    🧠 ModelContext

  • Represents a scratch space for reading/writing model instances

  • Think of it as an in-memory workspace

  • Must call try context.save() to persist changes to disk


  • Creating Contexts

  • Main UI context (automatically injected in SwiftUI via @Environment(\.modelContext))

  • Background context for heavy work

    @Environment(\.modelContext) var context   // UI context

    Task.detached {
       let backgroundContext = ModelContext(container)
       let user = User(name: "Async User")
       backgroundContext.insert(user)
       try? backgroundContext.save()
    }
     


1. Multiple Contexts

  • A ModelContainer can have multiple concurrent ModelContexts`

  • Useful for parallel work or background tasks


2. Thread Safety

  • ModelContext is not thread-safe

  • Use each context only on the actor it was created on

  • Always create a separate background context when working off the main actor


3. Merging Changes

  • Contexts are isolated

  • When one saves, others get notified and must refresh

  • Use:

    try context.fetch(FetchDescriptor<User>())
    context.refreshAllObjects()
     

  • Similar to Core Data’s merge behavior
    4. Conflict Resolution

  • If two contexts edit the same object → last writer wins

  • To avoid data loss:

    • Refresh objects before saving

    • Use background contexts for isolated work

    • Consider versioning if needed


5. Background Work

  • Use background contexts to do large inserts/updates without blocking UI

Example:
Task.detached {
   let ctx = ModelContext(container)
   for i in 0..<1000 {
       ctx.insert(User(name: "User \(i)"))
   }
   try? ctx.save()
}

6. Persistence & Crashes

  • Unsaved changes are lost if the app terminates/crashes

  • Always call save() after important changes


7. Multiple Containers

  • You can create multiple ModelContainers in one app

  • They are completely isolated (separate schemas and storage)

    ()) context.refreshAllObjects() Similar to Core Data’s merge behavior 4. Conflict Resolution If two contexts edit the same object → last writer wins To avoid data loss: Refresh objects before saving Use background contexts for isolated work Consider versioning if needed 5. Background Work Use background contexts to do large inserts/updates without blocking UI Example: Task.detached { let ctx = ModelContext(container) for i in 0..<1000 { ctx.insert(User(name: "User \(i)")) } try? ctx.save() } 6. Persistence & Crashes Unsaved changes are lost if the app terminates/crashes Always call save() after important changes 7. Multiple Containers You can create multiple ModelContainers in one app They are completely isolated (separate schemas and storage) 💡 Best Practices Always save() on critical points (onDisappear, backgrounding) Refresh contexts after background saves Use @MainActor for UI-bound contexts Use background contexts for long operations Keep ModelContainer as a singleton-like shared resource

Related Topics

Arjun

IOS Developer
Passionate iOS Developer with experience building intuitive and high-performance apps using Swift and SwiftUI. Skilled in designing clean architectures, integrating APIs, and optimizing user experience. Constantly exploring new Apple technologies to deliver impactful mobile solutions.