Skip to content

Instantly share code, notes, and snippets.

@kolodny
Created April 17, 2018 13:40
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kolodny/6fa6aa34a711d36e9de01cec44091557 to your computer and use it in GitHub Desktop.
Save kolodny/6fa6aa34a711d36e9de01cec44091557 to your computer and use it in GitHub Desktop.
Swift State Machine

Fully Exhaustive Swift State Machine

The following is a nice Swift pattern to make a vanilla Swift state machine:

class Machine {
  enum State {
    case foo
    case bar(String)
    case baz(Int)
  }
  var state: State {
    didSet {
      switch(oldValue, state) {
      case (.foo, .bar(let bar)):
        print("went from foo to bar(\(bar))")
      case (.bar(let bar), .foo):
        print("went from bar(\(bar)) to foo")
      case (.baz, .baz):
        print("baz to baz")
      case (_, .baz):
        print("ended up on baz")
      case (.foo, _),
           (_, .foo):
        print("started or ended on foo")
      case (.bar, _),
           (_, .bar):
        print("started or ended on bar")
      }
    }
  }
  init(state: State) {
    self.state = state
  }
}

Note that there isn't any (.baz, .foo), (.baz, .bar), or (.baz, _) cases since they're already handled in (_, .foo) and (_, bar) cases.

There's some sugar to also get this working with an optional state as follows:

var state: State? {
    didSet {
      switch(oldValue, state) {
      case (.foo?, .bar(let bar)?):
        print("went from foo to bar(\(bar))")
      case (.bar(let bar)?, .foo?):
        print("went from bar(\(bar)) to foo")
      case (.baz?, .baz?):
        print("baz to baz")
      case (_?, .baz?):
        print("ended up on baz")
      case (.foo?, _),
           (_, .foo?):
        print("started or ended on foo")
      case (.bar?, _?), // _? means anything not nil
           (_, .bar?): // _ means anything including nil
        print("started or ended on bar")
        case (nil, _),
             (_, nil):
        print("started or ended on nil")
      }
    }
  }

That doesn't mean that a 3 state machine needs to be so verbose, if you only care about 1 or 2 transitions you can have something along these lines:

class Promise<T> {
  indirect enum State { // indirect is needed due to some swift compiler bug
    case pending
    case resolved(T)
    case error(Error)
  }
  var state = State.pending {
    didSet {
      switch(oldValue, state) {
      case (.pending, .resolved(let value)):
        print("value is \(value)")
      case (.pending, .error(let error)):
        print("error is \(error)")
      case (_, .pending),
           (_, .resolved),
           (_, .error):
        print("invalid state change")
      }
    }
  }
}
enum MyError: Error {
  case someError
}
let promise = Promise<String>()
promise.state = .resolved("test") // value is test
promise.state = .error(MyError.someError) // invalid state change
promise.state = .pending // invalid state change
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment