Swift brought more power to a feature already known from the Objective-C days, enumerations.
However, unlike Objective-C enums which could enumerate related names only to Integer values, Swift enums are much more flexible and do not obligate the developer to provide a value (also known as a “raw value”) for each and every case as Objective-C enums did.
Swift enums also come with associated values, computed properties, instance and methods, initializers, conform to protocols and many more features that empower them to use them as models for our data. Yes, we can use enums almost as we can use classes and structs! And many times it is easier to maintain state and test our code by preferring enums over classes or structs (mainly for smaller amounts of data).
But there is no better way to demonstrate all the features and use cases of Swift enums, than using an example:
Let’s say that we want to model a pizza.
If we try a struct to do this, it would look like this:
The problem that emerges from this setup is that if the model contains properties related to state (as isBaked and isDelivered in the example above), it seems that an instance could possibly be (erroneously) in 2 different states at once while it should not. As you can easily understand, nothing protects us from setting isDelivered to true and isBaked to false to true, while this should not be allowed (as we have made the assumption that a Pizza can be delivered only if it has been baked previously).
Most models also come with or participate in actions that trigger state changes. Swift enums allow us to group associated values related to an action that triggers a change in the state of our model. Thus, we can protect ourselves from setting state properties to values that should not be set. The actions handle state changes now autonomously. In Swift enums, different states are grouped in cases that have associated values which are used as the state properties.
Actions are just mutating functions, which means that they can change (mutate) the variables of our enum and therefore, the current sate.
So we could transform the Pizza struct to enum in order to take advantage of the unique possibilities that Swift enums offer.
Now, each case in the enum represents a different and discrete state in which a Pizza instance can be.
In this implementation, the properties isBaked and isDelivered will be derived from the current state. We also declare a currentState variable that returns a literal mentioning the current state of a Pizza instance. We could say that the following two computed properties act the same way as the getters in an analogy with a normal Class.
But how do we change the state of an instance?
As we previously mentioned, actions trigger state changes. In enums, actions are modeled through mutating functions. Before we code the mutating functions we should think which state changes can be done and which state changes are not allowed. So, in this scenario let’s say that after we prepare a pizza, we can then add extra ingredients or bake it. Adding extra ingredients is optional. Also, we cannot deliver a pizza if it is not baked and we cannot add extra ingredients in a pizza that is already baked or that it has been already delivered. We would then prefer not to be able to deliver or bake a pizza twice.
These rules consist the transition function of our model.
We now have to convert these rules in cases according to input states. The mutating functions are shown below. As you can see, when we want to move from one state to another, we simply assign the new state to self. If the transition is not allowed for any reason, we print a relative message.
Then we can use the model we just created as shown below.
Important note: Instances should always be variables (declared with var) in order to be able to change states.
If we run the code below we should get the following output.
You can find the complete project in Swift playground format in this GitHub repository.
Thanks for your time! I hope you enjoyed.