Understanding Finite State Machines and Their Implementation in Swift
This article introduces finite‑state machines, explains their mathematical definition and classification, demonstrates a simple metro‑gate example, and provides two complete Swift implementations—one using object‑oriented design with protocols and classes and another using a functional style with generic transition structs and thread‑safe queues—followed by a real‑world keyboard‑state use case.
Introduction
Finite‑state machines (FSMs) are mathematical models of computation that consist of a finite set of states, a finite set of input symbols (events), a transition function, an initial state, and a set of final states. In practice we usually refer to deterministic finite automata (DFA) when discussing state machines.
Simple Real‑World Example
A metro gate can be modeled as a two‑state machine with states Locked and Unlocked . The events SwipeCard and PassBarrier trigger transitions between these states, illustrating how a concrete system can be described with a state diagram.
Formal Definition
A finite automaton M is defined by the 5‑tuple (Σ, Q, q₀, F, δ):
Σ – the input alphabet
Q – the set of states
q₀ ∈ Q – the start state
F ⊆ Q – the set of accepting (final) states
δ : Q × Σ → Q – the transition function
The transition function maps a current state and an input symbol to the next state, written as δ(q, x) = p.
Object‑Oriented Implementation (OOP)
The OOP approach abstracts the FSM into three core components: an Event protocol, a generic State class, and a generic StateMachine class that manages the current state and state transitions.
protocol Event: Hashable { }
class State<E: Event> {
weak var stateMachine: StateMachine<E>?
func trigger(event: E) {}
func enter() { stateMachine?.currentState = self }
func exit() {}
}
class StateMachine<E: Event> {
typealias FSMState = State<E>
var currentState: FSMState!
private var states: [FSMState] = []
init(initialState: FSMState) { currentState = initialState }
func setupStates(_states: [FSMState]) {
states = _states
states.forEach { $0.stateMachine = self }
}
func trigger(event: E) { currentState.trigger(event: event) }
func enter(_ stateClass: AnyClass) {
states.forEach { if type(of: $0) == stateClass { $0.enter() } }
}
}Concrete states (e.g., RunState and WalkState ) subclass State and override trigger , enter , and exit to perform logging and state changes.
enum RoleEvent: Event { case clickRunButton, clickWalkButton }
class RunState: State<RoleEvent> {
override func trigger(event: RoleEvent) {
switch event {
case .clickRunButton: break
case .clickWalkButton:
exit()
stateMachine?.enter(WalkState.self)
}
}
override func enter() { super.enter(); NSLog("====run enter=====") }
override func exit() { super.exit(); NSLog("====run exit=====") }
}
class WalkState: State<RoleEvent> { /* similar implementation */ }The machine is instantiated with an initial state and the two states are registered via setupStates . Events are dispatched with stateMachine.trigger(event: .clickRunButton) .
Functional Implementation
The functional style focuses on the transition function itself. A generic Transition struct captures the source state, destination state, triggering event, and optional pre‑ and post‑action blocks.
public typealias ExecutionBlock = () -> Void
public struct Transition
{
public let event: Event
public let source: State
public let destination: State
public let preAction: ExecutionBlock?
public let postAction: ExecutionBlock?
public init(with event: Event, from source: State, to destination: State, preBlock: ExecutionBlock?, postBlock: ExecutionBlock?) {
self.event = event
self.source = source
self.destination = destination
self.preAction = preBlock
self.postAction = postBlock
}
public func executePreAction() { preAction?() }
public func executePostAction() { postAction?() }
}The StateMachine class stores the current state, a dictionary of transitions keyed by event, and three dispatch queues (lock, working, callback) to guarantee thread‑safety.
class StateMachine
{
public var currentState: State { lockQueue.sync { internalCurrentState } }
private var internalCurrentState: State
private let lockQueue: DispatchQueue
private let workingQueue: DispatchQueue
private let callbackQueue: DispatchQueue
private var transitionsByEvent: [Event: [Transition
]] = [:]
public init(initialState: State, callbackQueue: DispatchQueue? = nil) {
self.internalCurrentState = initialState
self.lockQueue = DispatchQueue(label: "com.statemachine.queue.lock")
self.workingQueue = DispatchQueue(label: "com.statemachine.queue.working")
self.callbackQueue = callbackQueue ?? .main
}
public func add(transition: Transition
) { /* thread‑safe registration */ }
public func process(event: Event, execution: (() -> Void)? = nil, callback: ((Result
) -> Void)? = nil) {
// fetch transitions, execute preAction, execution block, change state, postAction, then callback
}
}Usage mirrors the OOP example: define enum EventType and enum StateType , create a StateMachineDefault , register two transitions (walk↔run) with logging blocks, and trigger events with stateMachine.process(event: .clickRunButton) .
Real‑World Case: Keyboard State Management
In a chat UI the keyboard can be in several states (prepare‑to‑edit, prepare‑to‑record, editing, showing panel). Events such as clickSwitchButton , clickMoreButton , tapTextField , and vcDidTapped are mapped to state transitions via a transition table. The same functional StateMachine registers these transitions and drives the UI by calling stateMachine.trigger(.clickSwitchButton) from UI callbacks.
Conclusion
Finite‑state machines provide a clear, extensible way to model UI logic and other systems with multiple discrete states. By using either an OOP hierarchy or a functional, generic transition‑based engine, developers can avoid tangled conditional code, adhere to the open‑closed principle, and easily add new states or events.
Sohu Tech Products
A knowledge-sharing platform for Sohu's technology products. As a leading Chinese internet brand with media, video, search, and gaming services and over 700 million users, Sohu continuously drives tech innovation and practice. We’ll share practical insights and tech news here.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.