The goal is to have well defined state transitions, and the ability to provide the next event to execute.
I'd like to know if this is a proper implementation of State Machine, considering how states and transitionTable are defined, and how I handle event as input and output via nextEvent
.
In many examples I cannot clearly define verbiage for state, so I defined them as verbs (notice ing suffix), as if it represents the ongoing progress of workflow. This may be wrong..
I also defined multiple events that could occur for a single state. For example, GetDeviceStatus and GetIntegrityStatus events both occur in RetrievingStatus state. This is also the case for Downloading state. You can see in the cases, when determining the next event, I need to check what the previous state was first.
If flawed, what are the pitfalls to my design, and how could it be improved? thanks.
enum ExampleState {
case Initiate
case Authorizing
case RetrievingStatus
case Downloading
case Confirming
case End
}
enum ExampleEvent {
case InitiateSequence
case GetAuthToken
case GetDeviceStatus
case GetIntegrityStatus
case DownloadFromServer
case DownloadToDevice
case Confirm
}
class StateMachine {
var oldState: ExampleState!
var currentState: ExampleState!
var currentEvent: ExampleEvent!
var table: [ExampleState: [ExampleEvent: ExampleState]] = [.Initiate: [.InitiateSequence: .Authorizing],
.Authorizing: [.GetAuthToken: .RetrievingStatus],
.RetrievingStatus: [.GetDeviceStatus: .Downloading, .GetIntegrityStatus: .Confirming],
.Downloading: [.DownloadFromServer: .Downloading, .DownloadToDevice: .RetrievingStatus],
.Confirming: [.Confirm: .End]]
func nextEvent(event: ExampleEvent) -> EventExecutor? {
let transitionState = table[currentState]![event]!
let oldState = currentState
switch (transitionState) {
case .Initiate:
currentState = .Initiate
return InitiateSequence()
case .Authorizing:
currentState = .Authorizing
return GetAuthToken()
case .RetrievingStatus:
currentState = .RetrievingStatus
switch (oldState) {
case .Authorizing: return GetDeviceStatus()
case .Downloading: return GetIntegrityStatus()
default: return nil
}
case .Downloading:
currentState = .Downloading
switch (oldState) {
case .RetrievingStatus: return DownloadFromServer()
case .Downloading: return DownloadToDevice()
default: return nil
}
case .Confirming:
currentState = .Confirming
return Confirm()
case .End:
currentState = .End
return nil
}
}
}
Use case
@objc func rxExecute() {
publisher
.map { self.stateMachine.nextEvent(event: $0) } // $0 == event that just completed
.flatMap { $0!.rxExecute() } // execute next event from state machine output
.subscribe(subscriber) // handles errors/success from completed event
}