Swift State Machines, Part 1

This is a two-parter. Here we’ll talk background and theory. If you want to dive right into code, feel free to skip directly to Part 2

Overloaded

Objects have a tough job. They’re required to represent 5 concepts (type, identity, behavior, state, and value) and only have slots to hold 4 of them (class, instantiation, methods, and properties). It’s like a game of musical chairs. State and Value are the only kids left standing, and there’s only a single chair labeled “property” still unoccupied. They’re both going to make a dash for it.

And this ends up complecting our apps. Here’s an example I’ve written probably 100× in production code:

var fetchTask:NSURLSessionTask?

func fetchThing(params:NSDictionary){
  if fetchTask == nil{
    fetchTask = myAPI.getRequest(params){
      ... 
    }
  }
}

That is: “Only make a request if I’m not already waiting for one I’ve made previously.”

It may not look it, but this is a disaster. Sure, it appears innocent enough right now. But what if we add a little more CRUD, like a POST wired up to a save button? We probably don’t want to fetch new data while trying to save the current stuff:

func fetchThing(params:NSDictionary){
  if fetchTask == nil && saveTask == nil{
	...
  }
}

Ok, uglier but still manageable. But that’s probably not the only place we were using fetchTask. We probably also had a spinner that was updated based on its value. Maybe we conditionally enabled buttons or fields based on its status. We have to remember to update all those as well.

And now what if we add a third task for sending a DELETE request…

Complected

The heart of the problem here is we’re using the fetchTask property for two very different things. We’re using it to hold a value1 (our network task) and represent state (the abstract concept of whether or not our object is doing something with the network). The two correlate to a surprising degree, so it’s not the end of the world. It happens all the time.

But then saveTask comes on the scene and, in addition to holding its own value, it also participates in the same “doing something with the network” state. Now, not only are our values tangled with our state, but our state is dispersed across multiple properties.

The solution is abstraction2. Because we’ve only had our property hammer, both value and state have looked very much like nails. If we give ourselves a new construct to model state, however, we can untangle everything by abstracting fetchTask’s and saveTaks’s state-ness into it.

What would such a construct look like?

Enter the State Machine

Wikipedia actually has a pretty good entry on state machines, so I won’t go too deeply into the concept. But generally, a state machine is an abstract data structure that is said to be in one of a set number of states, transitioning between them using well-defined rules.

For example, our object above might be said to have 5 states: Ready, Fetching, Saving, Success, and Failure.

We could set up a machine that models these possible states, the actual current state, and the rules for transitioning between them (i.e. moving from ReadyFetching is allowed, but moving from SavingFetching or FetchingFetching is not).

Then, not only would we have an canonical place to look to for the state of our object, but the transition rules built into our machine would protect us from getting into a state that’s invalid. Managing our object’s state would be easy!

Well, no. State is always going to be a tricky thing to get right, especially when you have a lot of it. But by detangling state from value, we give ourselves a fighting chance of making it simple enough to reason about3.

So are you sold? Ready to dive into this brave new world of finite-state automata? Tune in tomorrow to find out how surprisingly simple it is to implement all this in Swift.


1: I realize I’m playing it a little fast and loose with my concepts of “value”, “reference” and “identity” here. In the interest of overall clarity, I’m throwing out technical accuracy.↩︎

2: The solution is always abstraciton.↩︎

3: Watch this. Again. Seriously.↩︎