Enums--or enumerated types--in Swift have been around for a long time, but I never truly explored their potential until I stumbled across an article explaining that "enums can define any hierarchically organized data." So I decided to try using enums, not as I had traditionally used them, but as the very foundation of my data. Today, I invite you to explore the potential of these powerful types with me.

We usually use enums as a way to define a finite set of options in a given context, but in the following scenarios, I'll use them as linked lists (which is easy to do, thanks to indirect cases), and as a simplified sort of function builder (similar to how the new SwiftUI framework functions).

Let's show the power of enums by providing two examples: in the first example, we'll use enums instead of structs and classes to build a fun role-playing game, and in the second example, we'll use enums to create an app for model train enthusiasts. 

Using Enums to Create a Game

I love games, especially role-playing games. Nothing is better than entering a world of fantasy and taking your character along a perilous journey, watching them grow, and hopefully, not getting them killed along the way. And if enums can define any hierarchical data, then, we should be able to build game components with them.

For example, in our game, we can have an enum define the possible status conditions that our units can have:

enum Effect {

    case poison

    case blind

    case burn

    case bleed

    case freeze

    case damage(Int)

    case heal(Int)

}

Our units can have an array of effects to indicate what happens to them. This enum can also be used for applying effects when an attack happens. Some attacks, such as swords or axes, would only damage units, while other attacks could poison, bleed, or burn our foes. 

We can construct enums in such a way that they behave like semantic expressions. By chaining enums together, we can construct easy-to-understand definitions for the components in our game. Let’s say we’d like to create the logic for defining different attacks in our game, such as melee attacks or magic attacks. We can divide the attack into three parts: 

- The target

- The effect

- The attack type

So our structure would be something similar to: attack with “type” to “target” for ”effect.“

TARGET

We can start by defining an enum for possible targets:

enum TargetType {

    case ally

    case enemy

    case unit

}

An attack can be against an enemy, an ally (in the case of a heal spell, for example) or any unit. We can also define a given range constraint:

enum Range {

    case range(Int)

}

That would limit the range of our attack. We can also define the location of our targets:

enum Direction {

    case front

    case behind

    case around

}

And we can even define a constraint to the number or proximity of targets. For example:

enum Target {

    case target(TargetType, Range)

    case all(TargetType, Direction, Range)

}

Now, we can construct a very detailed description of the target, say for a melee weapon:

let meleeTarget: Target = .target(.enemy, .inRange(1))

We know that we want to target any enemy that’s within our range. Or if we wanted to perform a massive area-of-effect spell:

let spellTarget: Target = .all(.enemy, .around, .inRange(6))

EFFECT

But what will our attacks do? Well, we can define a trigger, maybe something that happens only if the hit is successful, or if the target dies. Again we can do all of this with enums:

enum TriggerType {

    case damage

    case hit

    case kill

}

The trigger type will tell us when we should trigger the effect of the attack.

enum Trigger {

    case end

    indirect case after(TriggerType, [Status], Trigger)

}

We can use nested enums to define multiple triggers and construct even more complex attacks.  For our melee attack, let’s say that it will deal one point of damage.   

let meleeTrigger: Trigger = .after(.hit, [.damage(1)], .end)

Or, we can have it be a poison-coated dagger that can also poison our foes, but only if we dealt damage with it.

let meleeTrigger: Trigger = .after(.hit, [.damage(1)], .after(.damage, [.poison], .end))

With this logic, we can construct any type of effect, and chaining enums lets us mix and match whatever we need.

ATTACKS

Ok, so now that we have our effects and targets, we can define a simple list of attacks:

enum Attack {

    case melee(Target, Trigger)

    case spell(String, Target, Trigger)

}

Maybe our heal spell is:

let healSpell: Attack = .spell("Heal", .target(.ally, .inRange(4)), .after(.hit, [.heal(1)], .end))

Our fire blast attack is:

let infernoSpell: Attack = .spell("Inferno", .all(.enemy, .around, .inRange(6)), .after(.hit, [.damage(3)], .after(.damage, [.burn], .end)))

We can now build on top of this and define enums for our weapons. Adding associated values can make it so that different weapons of the same type have different damage values (such as a wooden spear versus an iron spear):

enum Weapon {

    case spear(dmg: Int)

    case poisonDagger

    

    var attack: Attack {

        switch self {

        case let .spear(dmg):

            let meleeTarget: Target = .target(.enemy, .inRange(2))

            return .melee(meleeTarget, .after(.hit, [.damage(dmg)], .end))

        case .poisonDagger:

            let meleeTarget: Target = .target(.enemy, .inRange(1))

            return .melee(meleeTarget, .after(.hit, [.damage(1)], .after(.damage, [.poison], .end)))

        }

    }

}

We can do the same for spells. A magical tome can have a group of spells associated with it. Or we can define a new enum for different enemies in our game. Each enemy can perform different attack actions:

enum Enemy {

    case goblin

    case dragon

    

    var actions: [Attack] {

        switch self {

        case .goblin:

            return [Weapon.poisonDagger.attack]

        case .dragon:

            let bite: Attack = .melee(.target(.enemy, .inRange(1)), .after(.hit, [.damage(3)], .after(.kill, [.heal(1)], .end)))

            let dracarys: Attack = .spell("Inferno", .all(.enemy, .around, .inRange(6)), .after(.hit, [.damage(3)], .after(.damage, [.burn], .end)))

            return [bite, dracarys]

        }

    }

}

Using Enums to Create an App

Let’s now look into a different, more practical approach for using enums in apps. Say you’re a train enthusiast, and as such, you're creating an app to manage your train emporium. Again, we can use our trusty enums to create our models. Let’s start by creating an enum for the cargo of our trains. They can either hold some sort of material or be empty:

enum Cargo {

    case chemical(String, Int)

    case coal(Int)

    case ore(Int)

    case lumber(Int)

    case none

}

Then, to create our train model, we can do something like this:

enum Train {

    case tail(id: String, cargo: Cargo)

    indirect case car(id: String, cargo: Cargo, next: Train)

}

In this case, we can have an ID for each car, the cargo it hauls, and possibly the next car attached. This is simple and elegant. This also means that we can attach one train to another. 

So we can start creating our trains easily:

let train: Train = .car(id: "WX900", cargo: .lumber(20), next:

                   .car(id: "WX120", cargo: .ore(40), next:

                   .car(id: "TA763", cargo: .chemical("Radioactive Waste", 20), next:

                   .tail(id: "T800", cargo: .coal(100)))))

Now, if we want to know the total weight of the cargo, we can add a computed property to our enums:

enum Cargo {

    case chemical(String, Int)

    case coal(Int)

    case ore(Int)

    case lumber(Int)

    case none

    

    var tons: Int {

        switch self {

        case .chemical(_, let tons):

            return tons

        case .coal(let tons), .ore(let tons), .lumber(let tons):

            return tons

        case .none:

            return 0

        }

    }

}

And a couple more for our train model:

enum Train {

    case tail(id: String, cargo: Cargo)

    indirect case car(id: String, cargo: Cargo, next: Train)

    

    var numberOfCars: Int {

        switch self {

        case .car(id: _, cargo: _, next: let next):

            return 1 + next.numberOfCars

        default:

            return 1

        }

    }

    

    var totalTons: Int {

        switch self {

        case .car(id: _, cargo: let cargo, next: let next):

            return cargo.tons + next.totalTons

        case .tail(id: _, cargo: let cargo):

            return cargo.tons

        }

    }

}

So getting those properties becomes something as simple as:

train.numberOfCars // 4

train.totalTons // 180

Conclusion

As you can see, enums are a very powerful type in Swift. They are flexible enough to be used as building blocks for data and simple enough to understand and use. I had never thought of using enums instead of structs and classes, but now I imagine that we might (one day) end up doing enum-oriented programming.


Author

Eduardo Salinas

Eduardo Salinas is an iOS Developer at Avenue Code. He has been developing iOS apps since iOS 5. As an avid nerd, he enjoys board games, coffee, and the occasional ping pong match.


What You Need to Know about Swift Generics

READ MORE