Question 7
SwiftData, concurrency, and data races
How do you avoid data races when reading and writing SwiftData from Swift concurrency (async/await, actors, background work)?
Answer outline
The core rule in SwiftData is to never share a ModelContext across concurrent tasks. Each context must be owned by a single execution domain — @MainActor for UI work, a dedicated background context for background operations.
Pass IDs or plain values between tasks, not live model objects. Re-fetch in the destination context before reading or mutating — this keeps each context isolated and prevents data races.
Principles
- Never share a
ModelContext— give each task or actor its own context. - Keep UI work on
@MainActorand use a separate background context for heavy operations. - Pass IDs or plain values across context boundaries, not live model objects.
- Re-fetch in the destination context before mutating — treat contexts as non-transferable.
- Serialize writes through one owner when multiple async paths could update the same data.