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
Never share a ModelContext across concurrent tasks. Each context has one owner: the main actor for UI work, and a @ModelActor with its own background context for everything else.
Pass IDs or plain values between tasks, never live model objects. A PersistentIdentifier is Sendable and a model instance is not, so re-fetch by ID in the destination context before you read or mutate. That keeps each context isolated and rules out data races.
Principles
- Give each task or actor its own
ModelContextinstead of sharing one. - Keep UI work on the main actor's context, and give heavy background operations their own
@ModelActor. - Only IDs or plain values cross a context boundary, because a
PersistentIdentifierisSendableand a model instance isn't. - Re-fetch in the destination context before mutating, and treat model instances as non-transferable.
- Serialize writes through one owner when multiple async paths could update the same data.



