MVVM with @MainActor ViewModels

Each fortune mode has its own @MainActor-isolated ViewModel driving UI state.

Problem

SwiftUI views that mutate @Published properties from background contexts trigger runtime warnings and data races. Centralizing async work in a ViewModel without explicit actor isolation makes thread safety the caller’s burden.

Solution

Mark each ViewModel class @MainActor. All @Published properties are then guaranteed to mutate on the main thread without manual DispatchQueue.main.async wrappers. Each distinct feature area (EightBall, DailyFortune, FortuneCookie, Tarot) gets its own ViewModel so scope and state are clearly bounded.

Usage

Use when a SwiftUI feature owns non-trivial state that could be updated from an async context. One ViewModel per feature keeps state isolated and testable. Inject dependencies (services, managers) via the ViewModel initializer for preview and test compatibility.

In FortuneTeller, four ViewModels map 1:1 with fortune modes. Each encapsulates prediction generation, history persistence, and UI-state flags.

Trade-offs

  • Pro: Eliminates main-thread dispatch boilerplate; @Published mutations are safe by default; scoped ViewModels are individually testable.
  • Con: @MainActor propagates aggressively — any method called from the ViewModel also executes on the main actor, which can block if long synchronous work runs inside it.

Context

EightBall, DailyFortune, FortuneCookie, Tarot ViewModels.

Reusability

  • Score: 3/5
  • Used in: FortuneTeller

Source

Synced from ops-core learning data. Original source: FortuneTeller

See Also