The web content discusses the use of Swift enums as stateful data models, showcasing their advantages over traditional structs or classes for managing discrete states and state transitions in a more robust and error-proof manner.
Abstract
The article "Using Swift Enums as Stateful Data Models" delves into the enhanced capabilities of Swift enums compared to their Objective-C counterparts. It explains how Swift enums can encapsulate state and behavior without the risk of invalid states, thanks to associated values and mutating functions. The author illustrates this by modeling a pizza's lifecycle, demonstrating how enums can ensure that a pizza cannot be in conflicting states, such as being both undelivered and baked. The article emphasizes that enums can maintain state and facilitate testing more effectively than classes or structs for smaller datasets. It also provides examples of how to implement state changes through mutating functions and outlines the importance of transition functions to enforce valid state changes.
Opinions
The author suggests that using Swift enums for data modeling is preferable for maintaining state and testing code, especially when dealing with smaller amounts of data.
It is implied that traditional structs or classes may lead to erroneous states, as they do not inherently protect against conflicting property values.
The author advocates for the use of mutating functions within enums to handle state transitions, ensuring that only valid changes can occur.
The article conveys that enums with associated values can replace the need for classes or structs in certain scenarios, providing a more concise and error-resistant approach to state management.
The author expresses a preference for stateful enums over other data modeling approaches, highlighting their ability to encapsulate state and behavior in a discrete and controlled manner.
Using Swift Enums as Stateful Data Models
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.
Output
You can find the complete project in Swift playground format in this GitHub repository.