← All topics/Data persistence

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 ModelContext instead 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 PersistentIdentifier is Sendable and 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.