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;
@Publishedmutations are safe by default; scoped ViewModels are individually testable. - Con:
@MainActorpropagates 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