Architecture • 23 June 2026 • Written by Lochan Chugh
Building Predictable State Machines with Swift Macros
Building Predictable State Machines with Swift Macros
One of the greatest challenges in iOS architecture is preventing “Impossible States”—like a view showing both a loading spinner and an error message. Swift Macros provide a powerful new tool to automate the generation of robust, boilerplate-free state machines.
The Hook: The Enum Boilerplate
Implementing a strict state machine usually involves massive switch statements and repetitive transition logic. This boilerplate is not only tedious but also a breeding ground for bugs when a developer forgets to handle a specific edge case in one of the many transition methods.
The “Why”: Compile-Time Enforcement
By using an @Attached macro, we can generate valid transition methods automatically. If a transition from Loading to Success is defined, the macro generates the code; if a transition from Success back to Loading isn’t defined, the code won’t even compile.
The Implementation: The @StateMachine Macro
Imagine a macro that transforms a simple enum into a fully functional state machine.
@StateMachine
enum ViewState {
case idle
case loading
case success(data: [String])
case error(message: String)
// Macro generates:
// func toLoading() -> ViewState
// func toSuccess(data: [String]) -> ViewState
// var isLoading: Bool { get }
}
// Usage in a ViewModel
@Observable
class UserViewModel {
var state: ViewState = .idle
func fetchData() async {
state = state.toLoading() // Safe, generated transition
do {
let data = try await API.fetch()
state = state.toSuccess(data: data)
} catch {
state = state.toError(message: error.localizedDescription)
}
}
}
The macro ensures that you cannot accidentally move from idle to success without passing through loading, if that’s how your business logic is defined.
The Verdict: Clarity vs. Complexity
- Pros: Eliminates boilerplate; ensures consistency across the team; makes invalid states unrepresentable.
- Cons: Increases build times; requires the team to learn the Macro DSL.
- When to use: Complex flows like authentication, multi-step forms, or media playback.
Internal Connectivity
- Foundation: Roadmap Stage 7: Architectural Patterns
- Next Step: Micro-optimizing SwiftUI View Updates