Create a State Machine using Swift Enumerations
My Name is David House and I'm an iOS developer and instructor at Big Nerd Ranch. As view controllers become more complex, the need to manage the state of the view becomes more important. In this video we'll discuss how to use a state machine to manage our view state. After this video you'll know how to build a state machine using enumerations in Swift and how to apply that to your code.
Our example for this video is a simple to do list application. We're just making a request to a data source and showing the list on the screen. Let's look at the code of how we built this.
You can see we have a property that maintains the list of items and then this to do item fetch items method that will go get the data from our data source and return it. We just call, to the tableView, reload data there, and that's how the data is loaded.
Now we would like to make some improvements to this because, first of all, we are not handling the error that might come back. We're also not displaying a friendly no data found message instead of an empty list if there are no items.
And finally, because this is asynchronous, then it could take some time before the items appear and we would like to show some kind of loading indicator.
Now we could approach this by adding some properties to our view controller so that we know when we're loading, when we finished loading, and if there was an error. But adding these properties to our view controller over time adds a lot of complexity and then inside of our code, we're never sure exactly which state we're in without having to check all of these different properties to decide. Has there been an error and we have no data? Or when are we finished loading? These are questions that we would like to make very clear in our code when they are true.
To accomplish this, we're going to use a Swift enumeration to create a state machine and a state machine is just simply an object that can manage our state so that we know which state we're in, what the properties are while we're in that state and how we move from state to state. So let's start by adding a new enumeration to this file, which we'll call TodoListState. And now let's just add the states that we might be ... Well we know that we want model if we're loading. So we'll add a state for that. We'll also add the hasItems. So this is the successful call from the server and we have an array of these model object to do items.
Next we would have hasNoItems as a potential state. So, we've successfully loaded from the server, but there were just no items found. And finally, hasError. So this is where we made the call and we got some kind of error in response. Now you might be wondering why we're using items and error in these hasItems and hasError states.
So, this is a principle where we are making sure that the items and the error are only accessible when we are in those particular states. This keeps our code for the rest of the view controller very clear. It's only when I'm in the hasItem state that I can access the actual items. Otherwise, I can't access them. As you'll see later as we implement this, this becomes a really powerful way to keep our code very clean and very descriptive of what items I have access to.
All right, now we can look into something called signals and you can think of these as the outputs of our state machine. For this application we want to know two things. The first thing we need to know are how many items do we have? So we're going to create a function to track this. Now, think about this question because depending on the state that we're currently in will determine how I can answer this. In order to do that, we'll use a switch over self and now we can add in the cases where we don't have any items, so loading hasNoItems or hasError. And we don't really care about the error here, we just need to return zero, there are no items. Now if we have items, then what we can do is use a let here to get the items from the associated value and return those items count.
Next, let's create a function that will return an item given an index path. So we can use this in our table view to get an item if they're available. We're going to make this function return an optional, because for some states, this again, may not be valid.
So here we're going to use a guard statement and ensure that we're in the HasItems state, and also using let here allows us to get access to that associated value of items. If that is not our state, then we just can return nil. And if that is our state, then we could just return the item at that row.
As you can see, using the associated value here, we can only access it if we're in the hasItems state, which makes this code extremely safe to use
So the first thing is if there is an error, so we'll use an if let, then change our state to hasError with the air that we found. Otherwise, if we have items and the items count is greater than zero, then let's change ourself to our hasItems state with the items that we found. Otherwise, then we must be in the no items found state.
Great. Now we can move on and use this state machine in our view controller. Let's get rid of this items property and let's add in our state. Now, we're going to start in the loading state and then we're using didSet here so that whenever this state changes we can update our UI. In the case of loading, we'll show a loading indicator. In the case of noItems, we'll set a background label to no items found. In the case that we do have items, we'll make sure that our background view is cleared and then in the case of has error, we'll show the error. And, of course, we'll ask our table view to reload its data. So, now we've moved all of the code that needs to update our UI into one place. This was really helping us clean up this code.
Now we'll update our view to load and making sure that our state is in the loading state initially, this also makes sure that our didSet gets called because providing a default value for the property does not call the didSet.
Last thing in the view to load is just to let our state know that our task is finished and we can give it the result of the task. This will transition us into the correct state. And we want to be sure to use self here, since that is a property on ourself.
The last thing we need to do is replace the access of that items array to our functions that we created. We can ask the state how many items there are and we can ask the state to give us an item for an index path.
And that is all it takes to use a state machine in this view controller. Now our code is very clear on what state we're in, when we can access our items, and how to update the UI when this state changes. This has made this view controller extremely maintainable as time goes on and we need to add new features or change the behavior of it.
In this video we improved the state management of our view controller through the use of an enumeration. This same technique can be applied to other parts of your code as well, improving the maintainability by clearly describing the states as well as how to transition between them.
Once again, my name is David House. Thanks for watching and we'll see you here next time soon.