chourobin
11/21/2016 - 3:36 PM

A Simple Swift State Machine

A Simple Swift State Machine

import UIKit

class Example : UIView{
  private var machine:StateMachine<Example>!
  enum TrafficLight{
    case Stop, Go, Caution
  }

  
  required init(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    machine = StateMachine(initialState: .Stop, delegate: self)
  }

  
  @IBAction func tappedGo(sender:AnyObject){
    machine.state = .Go
  }
  
  
  @IBAction func tappedCaution(sender:AnyObject){
    machine.state = .Caution
  }
}



extension Example : StateMachineDelegateProtocol{
  typealias StateType = TrafficLight

  
  func shouldTransitionFrom(from: StateType, to: StateType) -> Should<StateType> {
    switch (from, to){
    case (.Stop, .Go), (.Caution, .Stop):
      return .Continue
    case (.Go, .Caution):
      return .Redirect(.Stop)
    default:
      return .Abort
    }
  }
  
  
  func didTransitionFrom(from: StateType, to: StateType) {
    switch to{
    case .Stop:
      backgroundColor = UIColor.redColor()
    case .Go:
      backgroundColor = UIColor.greenColor()
    case .Caution:
      backgroundColor = UIColor.yellowColor()
    }
  }
}

import Foundation

class StateMachine <P: StateMachineDelegateProtocol> {
    private unowned let delegate: P
    
    private var _state: P.StateType {
        didSet{
            delegate.didTransitionFrom(from: oldValue, to: _state)
        }
    }
    
    var state: P.StateType {
        get{
            return _state
        }
        set{
            // Can't be an observer because we need the option to CONDITIONALLY set state
            delegateTransitionTo(to: newValue)
        }
    }
    
    
    init(initialState: P.StateType, delegate: P) {
        // Set the primitive to avoid calling the delegate.
        _state = initialState
        self.delegate = delegate
    }
    
    private func delegateTransitionTo(to: P.StateType) {
        switch delegate.shouldTransitionFrom(from: _state, to: to) {
        case .Continue:
            _state = to
        case .Redirect(let newState):
            _state = to
            state = newState
        case .Abort:
            break;
        }
    }
}

protocol StateMachineDelegateProtocol: class {
    associatedtype StateType
    func shouldTransitionFrom(from: StateType, to: StateType) -> Should<StateType>
    func didTransitionFrom(from: StateType, to: StateType)
}

enum Should<T> {
    case Continue, Abort, Redirect(T)
}