Developing mobile Apps is a complex task, but it is our job as developers to solve that complexity. And a state machine is one tool that helps us construct simplicity out of complexity. So, in this Xcode tutorial in Swift, I will show you how to use a state machine when developing apps for iOS 9 and OS X 11 El Capitan.

A view controller can easily become a class embedding complex functionalities. For example, imagine a view controller that has to display a user’s profile from a social network. The first situation that we might encounter is that the data are still not available to the app at the moment when the viewcontroller’s view appears. In this case, we have 2 states that we need to handle: data are available and data are not available. The views and the corresponding layout depend on the current state. The simplest way of handling these states could be a boolean. For example, if data are available, the boolean is set to true , and if the data are not available the boolean is set to… mmmm, wait a second, this is not so simple. But what does “data are not available” mean exactly? Maybe, the data are still not available while we are asking for it from the remote server, or the data are not available because we are unable to retrieve the data from the remote server. Depending on this “unavailability”, we need to customize the UI depending on the real state of the data.

So, now we have 3 states, and our app needs to be able to transition from one state to another. For example, when we receive the data from the server, the UI could change from displaying an animated activity indicator to hiding the activity indicator. But if the application did not receive any data, we should still hide the activity indicator and inform the user that there is no data.

So our initial view controller has evolved. And now we not only need to track our current state, but we also need to track the state transition.

In iOS 9 and OS X 11 El Capitan, a new API is now available to create a state machine seamlessly. This new API is part of GameplayKit , which is usually used to develop video games. So now I will show you how to use this video game API to manage the state of your application.

Create states

We will use two classes: GKState and GKStateMachine . First, we need to create the states of our state machine. To do so, we need to subclass GKState .

Going back to our previous view controller example, we need a state. For example, to implement the state where we are retrieving the data from the server, we would subclass CGState as in the following example:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import UIKit import GameplayKit class RetrievingDataState : GKState { unowned var profileViewController : ViewController ? override func isValidNextState ( stateClass : AnyClass ) -> Bool { return ( stateClass == DataAvailableState . self ) || ( stateClass == DataNotAvailableState . self ) } override func didEnterWithPreviousState ( previousState : GKState ? ) { // Perform here the actions that you want to do with your UI when it enters this state like for example, showing and starting an activity indicator profileViewController ? . activityIndicator . startAnimating ( ) } override func willExitWithNextState ( nextState : GKState ) { // Perform here the actions that you want to do with your UI when it exits this state like for example, stopping the activity indicator. profileViewController ? . activityIndicator . stopAnimating ( ) }

The method isValidNextState: is used by the state machine to determine if a transition to the new state is valid or not. Return true for the states to which this state can transition. In our example, we can transition from the RetrievingDataState to both the DataAvailableState or the DataNotAvailableState , depending on the result of the server call.

didEnterWithPreviousState: and willExitWithNextState: allow us to specify what actions we want to perform when we enter into this state and when we exit. In the example, when we enter this state RetrievingDataState , we are starting an activity indicator to show that the App is doing some work, and when we exit, we are stopping it.

If later we decide that we want to show something else while we are retrieving the data, such as a label with a message, we can do that here. We can even decide to customize what actions to perform based on which state we are coming from, or to which state we are exiting to. This gives us a very fine grain control of what to do in each situation and also the possibility of making our view controllers more lightweight, because we are putting all that logic in a separate and well defined place.

One thing to notice is that I have added a property to keep a reference to the view controller, so we can execute its methods and access its properties from our state subclass. In this case, the reference must be unowned , because the view controller will hold a strong reference to the state machine and the state machine will also hold a strong reference to each of its states. If the state held a strong reference to the view controller, it would cause a circular retain. So, to avoid that, this reference has to be unowned.

To implement the rest of the states, let’s create another subclass of GKState :

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 class DataAvailableState : GKState { unowned var profileViewController : ViewController ? override func isValidNextState ( stateClass : AnyClass ) - & gt ; Bool { return stateClass == RetrievingDataState . self } override func didEnterWithPreviousState ( previousState : GKState ? ) { if let viewController = profileViewController { // Perform here the actions that you want to do with your UI when it enters this state. } } override func willExitWithNextState ( nextState : GKState ) { if let viewController = profileViewController { // Perform here the actions that you want to do with your UI when it exits this state. } } } class DataNotAvailableState : GKState { unowned var profileViewController : ViewController ? override func isValidNextState ( stateClass : AnyClass ) - & gt ; Bool { return stateClass == RetrievingDataState . self } override func didEnterWithPreviousState ( previousState : GKState ? ) { if let viewController = profileViewController { // Perform here the actions that you want to do with your UI when it enters this state. } } override func willExitWithNextState ( nextState : GKState ) { if let viewController = profileViewController { // Perform here the actions that you want to do with your UI when it exits this state. } } }

Create the state machine

Once we have the states, we can create the state machine.

We first create an instance of each of the previous states and assign to it a reference to the view controller:

1 2 3 4 5 6 7 8 9 let retrievingDataState = RetrievingDataState ( ) retrievingDataState . profileViewController = self let dataAvailableState = DataAvailableState ( ) dataAvailableState . profileViewController = self let dataNotAvailableState = DataNotAvailableState ( ) dataNotAvailableState . profileViewController = self

Finally, we create the state machine passing the states through an array:

1 2 let stateMachine = GKStateMachine ( states : [ retrievingDataState , dataAvailableState , dataNotAvailableState ] )

To start the machine just enter into one of the states. In our example, the first state is the RetrievingDataState .

1 stateMachine . enterState ( RetrievingDataState )

The state machine enters this state and executes the didEnterWithPreviousState: method of that state. So, the activity indicator will appear on the screen. When executing this method, the previousState is nil , because RetrievingDataState is the first state entered by the state machine.

When you receive the response from the server, depending on the result, you will change the state:

1 2 // When you receive updated data from the server stateMachine . enterState ( DataAvailableState )

As we are exiting the RetrievingDataState , willExitWithNextState: method will be executed and the activity indicator will stop. After that, didEnterWithPreviousState: of the DataAvailableState state will be executed, and so on.

Conclusion

In application development, we usually deal with multiple states. And things start to get really complex when we have to deal with so many different states: Is the Internet available? Is our server available? Are the data updated? Are the data valid? Is the app coming from the background? Has the app been launched as the result of opening a push notification?

Trying to handle all these states together is a very complex task. But GKState and CGStateMachine help us clearly define each state, and handle them simply.

Enjoy coding!!!

Vicente Vicens

(Visited 939 times, 1 visits today)