GKStateMachine: confusing terminology; feels kind of 'heavy'

Hello,


I've been using the GKStateMachine in a bit of sample code to sort of take it out for a test drive. First off, I'm happy to say it does what it is meant to do: I'm able to establish states and move from one state to another and have the appropriate methods called at the right times.


I'd like to mention the difficulties I have had in the process:


1. The terminology of GKStateMachine seems confused when it comes to a state. Sometimes a state seems to be an instance and sometimes it seems to be a class. For example, in this property and the init method what is referred to as a state is an instance of GKState.


    var currentState: GKState? { get }
    init(states: [GKState])


Whereas for these two methods, state refers to a class:


    func canEnterState(stateClass: AnyClass) -> Bool
    func enterState(stateClass: AnyClass) -> Bool


Of course, the parameter name is an indication that a class is wanted, but when calling these, the parameter name is not specified (nor should it be).


The confusion reaches its apex with this method which needs to mix both concepts, and resorts to introducing a second way of referring to a state:


    func stateForClass<StateType : GKState>(stateClass: StateType.Type) -> StateType?


In this method, the parameter is called a class and yet it is the same type (Class) as what is referred to as a state in the two previous methods.


I'm not going to suggest a fix for this because as my next point will illustrate, I don't think tying states to classes is a good idea.


2. I don't think tying states to classes is a good idea (surely you saw that coming...)


Naturally, the state machine needs a way to tell one state from another. But I think having a state identifier (which I suppose would have to be a string, but it would be pretty cool if it merely had to conform to the Equatable and maybe the Hashable protocols, so it could be something like a Swift enum) would be a better idea. If GKState had a property called identifier, it could even use NSStringFromClass([self class]) to provide the default value of the identifier and no extra work would need to be done by the user. But there would be greater clarity in the API; it might look something like:


    var currentState: GKState? { get }
    init(states: [GKState])
    func canEnterStateWithIdentifier(stateIdentifier: String) -> Bool
    func enterStateWithIdentifier(stateIdentifier: String) -> Bool


This is a little similar to the way different cells are identified in a UITableView. This would also allow someone to use multiple instances of the same class with different identifiers as different states in a machine (in case they were trying to do something clever).



3. I don't think requiring states to be classes is necessary.


I think GKState should be a protocol. That way, any class (or struct or enum, in Swift) could be a state. An app might already have some classes which represent objects which 'do stuff' and now wishes to have those actions ordered and co-ordinated with a state machine. To use a state machine with GKState as a class requires creating a whole 'nother set of classes to represent the states of the parallel classes which already exist. If GKState were a protocol, the GKState related methods could easily be implemented (posibly in a category or extension) on the existing classes. This is a little similar to the way an MKAnnotation works in MapKit: I can have an instance of a custom class and if I want to place an annotation which represents it on the map, I just have the custom class implement the MKAnnotation protocol, and write the necessary methods.


4. The state machine lacks events.


Many state machines allow the definition of an event which leads from one state to another. And when the machine is in the state, it is told an event occurred and it figures out, based on the current state and the given action, what the next state should be. The handy thing about this is that you should be able to define multiple states which use the same action to move to a succeeding state. Then the state machine will automatically go to the right next state. Currently, it is up to the user of the state machine to figure out what state to go to based on the current state and the event which has occurred. This could be added by subclassing, but it is a common enough feature of most state machines that I think it would be beneficial if it were built in.


5. I can't figure out the least awkward way of tying the willExit... or didEnter... methods in a GKState into the rest of my app


I chose to create a subclass of GKState which stored an instance of my view controller at init time. Then I had all my states inherit from this class, and init'ed them with the view controller. Then in the implementation of the willExit or didEnter, I was able to send messages to the view controller to have it update the UI or whatever. I considered passing one or two closures into the states or actually having the text fields and buttons which should be acted upon stored _in_ the states, but none of these seems very attractive. I'm going to have to think more about this one; any advice appreciated.


Thank you

Doug

Replies

Just FYI I believe states are identified by classes because that's the only way to ensure that each state is unique (within the same statemachine).


What you propose would open up statemachines to such issues as accidentally (or intentionally) using multiple instances of the same state class as different states in the same statemachine but little to no way for the state instances to differentiate from how (or where) they are being used within the statemachine.


If you continue that thought you may find that users could easily (and probably would because it's how they're used to write logic) end up writing conditional code within a given state that identifies where within the statemachine the current instance is, and then branch their logic based on that information. That's the very thing you are supposed to model through states and statemachines.

Hey Doug,


I came across your post while looking for some info on GKStateMachine. I have had almost the opposite experience after porting some code with a lot of switch statements, while maintaining my own states, and taking care of my own transitions, and so on. They could have gone with the String ids, but I really love the class implementations and code for each state is well and clearly defined and separated. It makes it a lot easier for me to trace and isolate bugs.


"4. The state machine lacks events."


I hope I get your mental model here, this is the way I see it. Having events within the state machine sort of enforces a certain "authority" structure where the state machine manages its states, and probably know a lot more about its children than it should. I have designed and used such state machines in the past and what I think the ios model does is to remove the SM as an "authority" figure. It only gets initialized with required states and all it does is state transitions. It does n't even know whether a transition is valid. That is left to the states themselves to define.


"5. I can't figure out the least awkward way of tying the willExit... or didEnter... methods in a GKState into the rest of my app"


Have you looked into Components and Entities? I use a combination of NotificationCenter postNotification in the willExit or didEnter methods and calling parent entities functions from the states. In some rare cases, I have singleton objects which I can call directly. My state machines are also components which belong to entities. In my case, my entities are game objects, but I suppose you can encapsulate your UI elements as entities. The challenge is keep things clean and generic so that you can actually reuse entities, components and states. For example, I have a move animation state which I can attach to different entities, and not having to re-write the same animation code all the time.


So, all in all, I think that GKEntity, GKComponent, GKStateMachine and GKState makes it easier to write modular and reusable code.


Cheers,

Fardin

Hi Doug,


Regarding your point (2.), I had a similar thought. What if stateMachine.states could be a Dictionary?


That's how they are using it: like a dictionary, where the class of each state is its key.


Since it's not a Dictionary (but should be), I did this inside my GKComponent subclass:



static let exampleStateKeyFactsArray_1 = ["foo is truly bar'd, dude","omfooginbar","froobardo barfoogins"]
static let exampleStateKeyFactsArray_2 = ["foo is NOT bar'd, dude","omfooginbar","froobardo NOT barfoogins"]

var lookUpTable_ClassByFacts:[NSArray:AnyClass] =
[ exampleStateKeyFactsArray_1 : myCustomGKStateSubclass.class ,
  exampleStateKeyFactsArray_2 : myOtherCustomGKStateSubclass.class ]


Then later I can do this:


myStateMachine.enterState(lookUpTable_ClassByFacts[myRuleSys.facts])


Using the result of the rule system to directly set which state to transition to. As it should be.


There are literally no "if" statements in my app. Anywhere.