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