Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Updated State Machine with Untangled Transitions and Behaviors
import Foundation
protocol StateMachineDelegate: class{
typealias StateType:StateMachineDataSource
func didTransitionFrom(from:StateType, to:StateType)
}
protocol StateMachineDataSource{
func shouldTransitionFrom(from:Self, to:Self)->Should<Self>
}
enum Should<T>{
case Continue, Abort, Redirect(T)
}
class StateMachine<P:StateMachineDelegate>{
private var _state:P.StateType{
didSet{
delegate.didTransitionFrom(oldValue, to: _state)
}
}
unowned let delegate:P
var state:P.StateType{
get{
return _state
}
set{ //Can't be an observer because we need the option to CONDITIONALLY set state
switch _state.shouldTransitionFrom(_state, to:newValue){
case .Continue:
_state = newValue
case .Redirect(let redirectState):
_state = newValue
self.state = redirectState
case .Abort:
break;
}
}
}
init(initialState:P.StateType, delegate:P){
_state = initialState //set the primitive to avoid calling the delegate.
self.delegate = delegate
}
}
enum NetworkState:StateMachineDataSource{
case Ready, Success(NSDictionary), Fail(NSError)
func shouldTransitionFrom(from:NetworkState, to: NetworkState) -> Should<NetworkState>{
switch (from, to){
case (.Ready, .Success), (.Ready, .Fail):
return .Redirect(.Ready)
case (.Success, .Ready), (.Fail, .Ready):
return .Continue
default:
return .Abort
}
}
}
class MyClass:StateMachineDelegate{
let machine:StateMachine<MyClass>!
init(){
machine = StateMachine(initialState: .Ready, delegate: self)
}
typealias StateType = NetworkState
func didTransitionFrom(from: StateType, to: StateType) {
switch (from, to){
case (.Ready, .Success(let json)):
updateModel(json)
case (.Ready, .Fail(let error)):
handleError(error)
case (_, .Ready):
reloadInterface()
default:
break
}
}
}
@MartinJNash

This comment has been minimized.

Copy link

commented Feb 10, 2015

I was able to completely get rid of the delegate's shouldTransitionFromState:ToState: method by instantiating the state machine with a list of valid transitions. This will prevent the urge to put side effects in the 'should' method.

import Foundation

class StateMachine<P:StateMachineDelegateProtocol>{

    private unowned let delegate:P
    private let validTransitions: [P.StateType: [P.StateType]]

    private var _state:P.StateType{
        didSet{
            delegate.didTransitionFrom(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
            attemptTransitionTo(newValue)
        }
    }


    init(initialState:P.StateType, delegate:P, validTransitions: [P.StateType: [P.StateType]]){
        _state = initialState //set the primitive to avoid calling the delegate.
        self.validTransitions = validTransitions
        self.delegate = delegate
    }


    private func attemptTransitionTo(to:P.StateType){
        if let validNexts = validTransitions[_state] {
            if contains(validNexts, to) {
                _state = to
            } else {
                // error, etc
            }
        }
    }
}

protocol StateMachineDelegateProtocol: class {
    // make this be hashable so we can pass in a dictionary
    typealias StateType : Hashable

    func didTransitionFrom(from:StateType, to:StateType)
}





import UIKit

class Example : UIView{

    private var machine:StateMachine<Example>!

    enum TrafficLight : Int {
        case Stop, Go, Caution
    }


    required init(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)

        let tx = [
            TrafficLight.Stop: [TrafficLight.Go],
            TrafficLight.Caution: [TrafficLight.Stop],
            TrafficLight.Go: [TrafficLight.Caution]
        ]

        machine = StateMachine(initialState: .Stop, delegate: self, validTransitions: tx)
    }


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


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

extension Example : StateMachineDelegateProtocol {

    typealias StateType = TrafficLight

    func didTransitionFrom(from: StateType, to: StateType) {
        switch to{
        case .Stop:
            backgroundColor = UIColor.redColor()
        case .Go:
            backgroundColor = UIColor.greenColor()
        case .Caution:
            backgroundColor = UIColor.yellowColor()
        }
    }

}
@happyjem

This comment has been minimized.

Copy link

commented Feb 25, 2015

these seems to compile error in Xcode 6.3 beta 2 (I use Swift 1.2)

In MyClass

'self' used before all stored properties are initialized
'self.machine' used before being initialized

@happyjem

This comment has been minimized.

Copy link

commented Feb 25, 2015

let machine:StateMachine! ====> var machine:StateMachine!
Then compiled to success

@happyjem

This comment has been minimized.

Copy link

commented Feb 25, 2015

I moved to "unowned let delegate:P" above "private var _state:P.StateType" then got compiler error that "Command /Applications/Xcode-beta.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swiftc failed with exit code 1"

I don't Know what´s wrong with this function

@iliaskarim

This comment has been minimized.

Copy link

commented May 8, 2015

I think @MartinJNash is on the right path by removing the should delegate method and transforming its representation into data.

I also noticed @algal's comments on the previous iteration of the state machine. He thought that the delegate pattern was a mismatch for the generics in use. I tend to agree.

I finally ended up with this (Traffic Light example in link, only the State and State Machine are reproduced below for brevity)

protocol State {

    func shouldTransition(toState: Self) -> Bool
}


class StateMachine<T: State>{

    typealias TransitionObservation = ((from: T, to: T) -> ())

    var state: T {
        get { return _state }
        set {
            if (self.state.shouldTransition(newValue)) {
                let oldValue = _state
                _state = newValue
                self.transitionObservation?(from: oldValue, to: newValue)
            }
        }
    }

    private var _state: T

    private var transitionObservation: TransitionObservation?

    init(initialState:T, observer: TransitionObservation? = nil) {
        _state = initialState
        self.transitionObservation = observer
    }
}

It has a clear delineation between State, State Machine, and Observer, meaning:

  1. The State can explicitly declare which transitions are valid
  2. There is no possibility of shoehorning State change side effects into the State Machine itself

I've also added the feature to add as many observers as one likes (see my gist), which can be a very useful thing in a world without KVO!

Let me know what you think

@Codeglee

This comment has been minimized.

Copy link

commented Jan 3, 2017

@ILI4S Your gist is a 404, I'd be interested in seeing what you came up with though.

@iOSSmith

This comment has been minimized.

Copy link

commented Jan 19, 2018

This is gold!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.