-
-
Save jemmons/f30f1de292751da0f1b7 to your computer and use it in GitHub Desktop.
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 | |
} | |
} | |
} | |
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
let machine:StateMachine! ====> var machine:StateMachine!
Then compiled to success
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
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:
- The State can explicitly declare which transitions are valid
- 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
@ILI4S Your gist is a 404, I'd be interested in seeing what you came up with though.
This is gold!
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.