Fundamentals 18 min read

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.

Sohu Tech Products
Sohu Tech Products
Sohu Tech Products
Understanding Finite State Machines and Their Implementation in Swift

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.

Swiftfunctional programmingObject-Oriented ProgrammingFinite State Machinestate machine implementation
Sohu Tech Products
Written by

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.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.