released Sat, 17 Oct 2015
Swift Version 5.1

Advanced and Practical Enum usage in Swift

When and how to use enums in Swift? This is a detailed practical overview of all the possibilities enums can offer you.

Introduction

When and how to use enums in Swift? This is a detailed practical overview of all the possibilities enums can offer you.

Similarly to the switch statement, enum's in Swift may at first glance look like a slightly improved variant of the well known C enum statement. I.e. a type that allows you to define that something is "one of something more general". However, the particular design decisions behind Swift's enums allow it to be used in a much wider range of practical scenarios than plain C enums. In particular, they're great tools to clearly manifest the intentions of your code.

In this post, we'll first look at the syntax and possibilities of using enum, and will then use them in a variety of (hopefully) practical, real world scenarios to give a better idea of how and when to use them. We'll also look a bit at how enums are being used in the Swift Standard library.

Before we dive in, here's a definition of what enums can be. We'll revisit this definition later on:

"Enums declare types with finite sets of possible states and accompanying values. With nesting, methods, associated values, and pattern matching, however, enums can define any hierarchically organized data."

First, though, what are enums?

Diving In

A short overview of how to define and use enums.

We're working on a game, and the player can move in four directions. So our player movement is restricted. He can only go right or left. You could model that in the following manner:

if movement == "left" { ... }
else if movement == "right" { ...}

However this is dangerous, what if we have a typo in our code and movement is neither left nor right but leeft?. Wouldn't it be cool if the compiler would point out if we have a typo like that? You could just write:

let moveLeft = 0
let moveRight = 1
if movement == moveLeft { ... }
else if movement == moveRight { ... }

This would solve our problem of typos in the code, but if we had more movements, it would be an easy bug to forget to handle all the movements. Imagine somebody extends the code in a couple of months and adds two new movements:

let moveUp = 0
let moveDown = 1

This developer forgets to update the if logic, though, so now we have more movements but we forgot to handle them. Wouldn't it be great if the compiler would tell us if we forgot to handle all the cases of our movements? That's what the enum type is for:

Defining Basic Enums

Enums tell Swift that a particular set of cases belong together. Our movement enum could look like this:

enum Movement {
case left
case right
}

It is considered proper style in Swift to always use lowercase for the enum case name

Swift's switch allows you to handle all the states of an enum:

let myMovement = Movement.left
switch myMovement {
case Movement.left: player.goLeft()
case Movement.right: player.goRight()
}

If we would add another case (such as up), then the compiler would complain.

There's also a really nice shortcut in Swift. Since the compiler knows that myMovement is of type Movement you don't have to write that out explicitly. This also works:

switch myMovement {
case .left: player.goLeft()
case .right: player.goRight()
}

It is considered good style to not write out the enum name. Theyre may be situations where you have to do it in order to please the Compiler though.

Handling Enums

Besides the switch statement above, Swift also offers many more ways of handling enum types. Many of which can be found in our Pattern Matching Guide, some of the will also be handled in this guide on enum.

Enum values

Sometimes may want to have a value assigned to each enum case. This is useful if the enum itself indeed relates to something which can be expressed in a different type. C allows you to assign numbers to enum cases. Swift gives you much more flexibility here:

// A pretty useless enum
enum Binary {
  case zero = 0
  case one = 1
}

// You can also map to strings
enum House: String {
    case baratheon = "Ours is the Fury"
    case greyjoy = "We Do Not Sow"
    case martell = "Unbowed, Unbent, Unbroken"
    case stark = "Winter is Coming"
    case tully = "Family, Duty, Honor"
    case tyrell = "Growing Strong"
}

// Or to floating point (also note the fancy unicode in enum cases)
enum Constants: Double {
    case π = 3.14159
    case e = 2.71828
    case φ = 1.61803398874
    case λ = 1.30357
}

For String and Int types, you can even omit the values and the Swift compiler will do the right thing:

// mercury = 1, venus = 2, ... neptune = 8
enum Planet: Int {
    case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune
}

// north = "north", ... west = "west"
enum CompassPoint: String {
    case north, south, east, west
}

Swift supports the following types for the value of an enum:

  • Integer
  • Floating Point
  • String
  • Boolean

You can support more types by implementing a specific protocol.

If you want to access the values, you can do so with the rawValue property:

let bestHouse = House.stark
print(bestHouse.rawValue)
// prints "Winter is coming"

However, there may also be a situation where you want to construct an enum case from an existing raw value. In that case, there's a special initializer for enums:

enum Movement: Int {
    case left = 0
    case right = 1
    case top = 2
    case bottom = 3
}
// creates a movement.Right case, as the raw value for that is 1
let rightMovement = Movement(rawValue: 1)

If you use the rawValue initializer, keep in mind that it is a failable initializer, i.e. you get back an Optional, as the value you're using may not map to any case at all, say if you were to write Movement(rawValue: 42).

Nesting Enums

If you have specific sub type requirements, you can also logically nest enums in an enum. This allows you to contain specific information on your enum case within the actual enum. Imagine a character in an RPG. Each character can have a weapon, all characters have access to the same set of weapons. All other instances in the game do not have access to those weapons (they're trolls, they just have clubs).

enum Character {
  enum Weapon {
    case bow
    case sword
    case lance
    case dagger
  }
  enum Helmet {
    case wooden
    case iron
    case diamond
  }
  case thief
  case warrior
  case knight
}

Now you have a hierachical system to describe the various items that your character has access to.

let character = Character.thief
let weapon = Character.Weapon.bow
let helmet = Character.Helmet.iron

If you add initializers for the nested enum types, you can still benefit from not having to type out the the long description. Imagine a function that calculates how strong a character is, based on the character, the weapon, and the helmet:

func strength(of character: Character, 
     with weapon: Character.weapon, 
     and armor: Character.Helmet) {
     return 0
}

// You can still call it like this:
strength(of: .thief, with: .bow, and: .wooden)

This is still really clear and easy to understand.

Containing Enums

In a similar vein, you can also embed enums in structs or classes. Continuing with our previous example:

struct Character {
   enum CharacterType {
    case thief
    case warrior
    case knight
  }
  enum Weapon {
    case bow
    case sword
    case lance
    case dagger
  }
  let type: CharacterType
  let weapon: Weapon
}

let warrior = Character(type: .warrior, weapon: .sword)

This really helps in keeping related information together.

Associated Values

Associated values are a fantastic way of attaching additional information to an enum case. Say you're writing a trading engine, and there're two different possible trade types. buy and sell. Each of them would be for a specific stock and amount:

Simple Example

enum Trade {
    case buy
    case sell
}
func trade(tradeType: Trade, stock: String, amount: Int) {}

However, the stock and amount clearly belong to the trade in question, having them as separate parameters feels unclean. You could embed it into a struct, but associated values allow for a much cleaner solution:

enum Trade {
    case buy(stock: String, amount: Int)
    case sell(stock: String, amount: Int)
}
func trade(type: Trade) {}

This defines two cases, buy and sell. Each of these cases has additional values attached to it: The stock and amount to buy / sell. These cases cannot exist without these additional values. You can't do this:

let trade = Trade.buy

You always have to initialize these cases with the associated values:

let trade = Trade.buy("APPL", 500)

Pattern Matching

If you want to access this information, again, pattern matching comes to the rescue:

let trade = Trade.buy(stock: "AAPL", amount: 500)

if case let Trade.buy(stock, amount) = trade {
    print("buy \(amount) of \(stock)")
}

Here, you're telling the Swift compiler the following: "If the trade is of type Trade.buy with the two values stock and amount, then let those two variables exist with the values". You kinda have to read this if line from right to left.

There's an alternative way of writing this with two let statements:

if case Trade.buy(let stock, let amount) = trade {
  ...
}

Labels

Associated values do not require labels. You can just denote the types you'd like to see in your enum case.

enum Trade {
   case buy(String, Int)
   case sell(String, Int)
}

// Initialize without labels
let trade = Trade.sell("APPL", 500)

If you don't add labels, you also don't write them out when creating a case. If you add them, though, you'll have to always type them out when creating your enum cases.

Use Case Examples

Associated Values can be used in a variety of ways. What follows is a list of short examples in no particular order.

// Cases can have different values
enum UserAction {
  case openURL(url: NSURL)
  case switchProcess(processId: UInt32)
  case restart(time: NSDate?, intoCommandLine: Bool)
}

// Or imagine you're implementing a powerful text editor that allows you to have
// multiple selections, like Sublime Text here:
// https://www.youtube.com/watch?v=i2SVJa2EGIw
enum Selection {
  case none
  case single(Range<Int>)
  case multiple([Range<Int>])
}

// Or mapping different types of identifier codes
enum Barcode {
    case UPCA(numberSystem: Int, manufacturer: Int, product: Int, check: Int)
    case QRCode(productCode: String)
}

// Or, imagine you're wrapping a C library, like the Kqeue BSD/Darwin notification
// system: https://www.freebsd.org/cgi/man.cgi?query=kqueue&sektion=2
enum KqueueEvent {
    case userEvent(identifier: UInt, fflags: [UInt32], data: Int)
    case readFD(fd: UInt, data: Int)
    case writeFD(fd: UInt, data: Int)
    case vnodeFD(fd: UInt, fflags: [UInt32], data: Int)
    case errorEvent(code: UInt, message: String)
}

// Finally, all user-wearable items in an RPG could be mapped with one
// enum, that encodes for each item the additional armor and weight
// Now, adding a new material like 'Diamond' is just one line of code and we'll have the option to add several new Diamond-Crafted wearables.
enum Wearable {
    enum Weight: Int {
        case light = 1
        case mid = 4
        case heavy = 10
    }
    enum Armor: Int {
        case light = 2
        case strong = 8
        case heavy = 20
    }
    case helmet(weight: Weight, armor: Armor)
    case breastplate(weight: Weight, armor: Armor)
    case shield(weight: Weight, armor: Armor)
}
let woodenHelmet = Wearable.helmet(weight: .light, armor: .light)

Methods and Properties

Swift enum types can have methods and properties attached to them. This works exactly like you'd do it for class or struct types. Here is a very simple example:

enum Transportation {
  case car(Int)
  case train(Int)

  func distance() -> String {
    switch self {
    case .car(let miles): return "\(miles) miles by car"
    case .train(let miles): return "\(miles) miles by train"
    }
  }
}

The main difference to struct or class types is that you can switch on self within the method in order to calculate the output.

Here is another, more involved, example where we use the enum values to determine the numerical attributes of a character in a method.

enum Wearable {
    enum Weight: Int {
        case light = 1
    }
    enum Armor: Int {
        case light = 2
    }
    case helmet(weight: Weight, armor: Armor)

    func attributes() -> (weight: Int, armor: Int) {
       switch self {
       case .helmet(let w, let a): 
          return (weight: w.rawValue * 2, armor: a.rawValue * 4)
       }
    }
}
let woodenHelmetProps = Wearable.helmet(weight: .light, armor: .light)
    .attributes()

Properties

Enums don't allow for adding stored properties. This means the following does not work:

enum Device {
  case iPad
  case iPhone
  
  let introduced: Int
}

Here, we'd like to store an Apple device together with the year when it was introduced. However, this does not compile.

Even though you can't add actual stored properties to an enum, you can still create computed properties. Their contents, of course, can be based on the enum value or enum associated value. They're read-only though.

enum Device {
  case iPad,
  case iPhone

  var introduced: Int {
    switch self {
    case .iPhone: return 2007
    case .iPad: return 2010
     }
  }
}

This works great as the year of the introduction of an Apple device never changes. You couldn't use this if you'd like to store mutable / changing information. In those cases you'd always use associated values:

enum Character {
  case wizard(name: String, level: Int)
  case warior(name: String, level: Int)
}

Also, you can always still add properties for easy retrieval of the associated value:

extension Character {
  var level: Int {
    switch self {
    case .wizard(_, let level): return level
    case .warior(_, let level): return level
    }
  }
}

Static Methods

You can also have static methods on enums, i.e. in order to create an enum from a non-value type.

Static methods are methods you can call on the name of the type instead of a specific instance of the type. In this example we add a static method to our enum Device which returns the most recently released device:

enum Device {
  static var newestDevice: Device {
    return .appleWatch
  }

  case iPad,
  case iPhone
  case appleWatch
}

Mutating Methods

Methods can be declared mutating. They're then allowed to change the case of the underlying self parameter. Imagine a lamp that has three states: off, low, bright where low is low light and bright a very strong light. We want a function called next that switches to the next state:

enum TriStateSwitch {
    case off, low, bright
    mutating func next() {
        switch self {
        case .off:
            self = low
        case .low:
            self = .bright
        case high:
            self = off
        }
    }
}
var ovenLight = TriStateSwitch.low
ovenLight.next()
// ovenLight is now equal to .bright
ovenLight.next()
// ovenLight is now equal to .off

Before we look at advanced enum usage, we'll do a brief recap of what we've learned in this section so far.

Recap

Lets have another look at the explanation we gave at the beginning and see if it became clearer now.

Enums declare types with finite sets of possible states and accompanying values. With nesting, methods, associated values, and pattern matching, however, enums can define any hierarchically organized data.

The definition is a lot clearer now. Indeed, if we add associated values and nesting, an enum case is like a closed, simplified struct. The advantage over structs being the ability to encode categorization and hierachy:

// Struct Example
struct Point { let x: Int, y: Int }
struct Rect { let x: Int, y: Int, width: Int, height: Int }

// Enum Example
enum GeometricEntity {
   case point(x: Int, y: Int)
   case rect(x: Int, y: Int, width: Int, height: Int)
}

The addition of methods and static methods allow us to attach functionality to an enum without having to resort to free functions

// C-Like example
enum Trade {
   case buy
   case sell
}
func order(trade: Trade)

// Swift Enum example
enum Trade {
   case buy
   case sell
   func order() {}
}

Advanced Enum Usage

The enum type is one of Swift's most distinctive features. We already saw a lot of different use cases. However, There's much more that enums can do.

They can be used with protocols, just like other Swift types, they can have extensions, they can be generic, and much more. This chapter will introduce these interesting enum features.

We will start by having a look at conforming enums to protocols.

Protocols

We already mentioned the similarity between the struct and enum types. In addition to the ability to add methods, Swift also allows you to use Protocols and Protocol Extensions with enums.

Swift protocols define an interface that types can conform to. In this case our enum can conform to it. For a start, let's take a protocol from the Swift standard library.

CustomStringConvertible is a type with a customized textual representation suitable for printing purposes:

protocol CustomStringConvertible {
  var description: String { get }
}

It has only one requirement, namely a getter for a string. We can implement this on an enum quite easily:

enum Trade: CustomStringConvertible {
   case buy, sell
   var description: String {
       switch self {
       case .buy: return "We're buying something"
       case .sell: return "We're selling something"
       }
   }
}

Some protocol implementations may need internal state handling to cope with the requirements. Imagine a protocol that manages a bank account:

protocol AccountCompatible {
  var remainingFunds: Int { get }
  mutating func addFunds(amount: Int) throws
  mutating func removeFunds(amount: Int) throws
}

You could easily fulfill this protocol with a struct, but in the context of your application, an enum is the more sensible approach.

However, you can't add properties like var remainingFunds: Int to an enum, so how would you model that? The answer is actually easy, you can use associated values for this:

enum Account {
  case empty
  case funds(remaining: Int)
  case credit(amount: Int)

  var remainingFunds: Int {
    switch self {
    case .empty: return 0
    case .funds(let remaining): return remaining
    case .credit(let amount): return amount
    }
  }
}

To keep things clean, we can then define the required protocol functions in a protocol extension on the enum:

extension Account: AccountCompatible {

  mutating func addFunds(amount: Int) {
    var newAmount = amount
    if case let .funds(remaining) = self {
      newAmount += remaining
    }
    if newAmount < 0 {
      self = .credit(newAmount)
    } else if newAmount == 0 {
      self = .empty
    } else {
      self = .funds(remaining: newAmount)
    }
  }

  mutating func removeFunds(amount: Int) throws {
    try self.addFunds(amount * -1)
  }

}

var account = Account.funds(remaining: 20)
try? account.addFunds(amount:10)
try? account.removeFunds(amount:15)

As you can see, we implemented all the protocol requirements by storing our values within our enum cases. A very nifty side effect of this is, that now you can test for an empty account with a simple pattern match all over your code base. You don't have to see whether the remainingFunds are zero.

We're also implementing the protocol in an extension. We'll learn more about extensions on enum types in the next chapter.

Extensions

Take the following enum:

enum Entity {
    case soldier(x: Int, y: Int)
    case tank(x: Int, y: Int)
    case player(x: Int, y: Int)
}

As we just saw, enums can also be extended. There're two use cases for this. You've already seen the first one: Conforming to a protocol.

extension Entity: CustomStringConvertible {
  var description: String {
    switch self {
    case let .soldier(x, y): return "\(x), \(y)"
    case let .tank(x, y): return "\(x), \(y)"
    case let .player(x, y): return "\(x), \(y)"
    }
  }
}

The other use case is keeping enum cases and methods separate, so that a reader of your code can easily digest the enum and afterwards read the methods.

extension Entity {
   mutating func move(dist: CGVector) {}
   mutating func attack() {}
}

Extending

Extensions also allow you to add useful code to existing enum types. Either from the Swift standard library, or from third party frameworks, or from yourself if you happen to have a big codebase.

For example, we can extend the standard library Optional type in order to add useful extensions. If you'd like to learn more about this, we have an article that explains this in more detail.

extension Optional {
    /// Returns true if the optional is empty
    var isNone: Bool {
        return self == .none
    }
}

Another example would be addign a convenience extension to one of your own enum types that is fileprivate so that you'd use it only within a specific file:

fileprivate extension Entity {
  mutating func replace(to: Entity) {
    self = entity
  }
}

Here, we have an extension to your Entity that allows to replace it with a different entity. This would only be used deep within your game engine which is why the scope is limited to one file.

Generic Enums

Enums can also be defined over generic parameters. You'd use them to adapt the associated values of an enum. The simplest example comes straight from the Swift standard library, namely the Optional type. You probably mostly use it with optional chaining (?), if let, guard let, or switch, but syntactically you can also use Optionals like so:

let aValue = Optional<Int>.some(5)
let noValue = Optional<Int>.none
if noValue == Optional.none { print("No value") }

This is the direct usage of an Optional without any of the syntactic sugar that Swift adds in order to make your life a tremendous amount easier. If you look at the code above, you can probably guess that internally the Optional is defined as follows 1:

// Simplified implementation of Swift's Optional
enum MyOptional<T> {
  case some(T)
  case none
}

What's special here is, that the enum's associated values take the type of the generic parameter T, so that optionals can be built for any kind you wish to return.

Enums can have multiple generic parameters. Take the well-known Either type which is not part of Swift's standard library but implemented in many open source libraries as well as prevalent in other functional programming languages like Haskell or F#. The idea is that instead of just returning a value or no value (née Optional) you'd return either one of two different values.

For example, if you parse user input, the user could enter a name or a number, in that case the type of Either would be Either<String, Int>.

enum Either<T1, T2> {
  case left(T1)
  case right(T2)
}

Finally, all the type constraints that work on classes and structs in Swift also work on enums. Here, we have a type Bag that is either empty or contains an array of elements. Those elements have to be Equatable.

enum Bag<T: Sequence> where T.Iterator.Element==Equatable {
    case empty
    case full(contents: [T)]
}

Recursive / Indirect Types

Indirect types allow you to define enums where the associated value of a case is the very same enum again.

As an example, consider that you want to define a file system representations with files and folders containing files. If File and Folder were enum cases, then the Folder case would need to have an array of File cases as it's associated value. Since this is a recursive operation, the compiler has to make special preparations for it. Quoting from the Swift documentation:

Enums and cases can be marked indirect, which causes the associated value for the enum to be stored indirectly, allowing for recursive data structures to be defined.

So to implement our FileNode enum, we'd have to write it like this:

enum FileNode {
  case file(name: String)
  indirect case folder(name: String, files: [FileNode])
}

The indirect keyword tells the compiler to handle this enum case indirectly. You can also add the keyword for the whole enum. As an example imagine mapping a binary tree:

indirect enum Tree<Element: Comparable> {
    case empty
    case node(Tree<Element>,Element,Tree<Element>)
}

This is a very powerful feature that allows you to map complex relationships in a very clean way with an enum.

Custom Data Types

If we neglect associated values, then the value of an enum can only be an Integer, Floating Point, String, or Boolean. If you need to support something else, you can do so by implementing the ExpressibleByStringLiteral protocol which allows the type in question to be serialized to and from String.

As an example, imagine you'd like to store the different screen sizes of iOS devices in an enum:

enum Devices: CGSize {
   case iPhone3GS = CGSize(width: 320, height: 480)
   case iPhone5 = CGSize(width: 320, height: 568)
   case iPhone6 = CGSize(width: 375, height: 667)
   case iPhone6Plus = CGSize(width: 414, height: 736)
}

However, this doesn't compile because CGSize is not a literal and can't be used as an enum value. Instead, what you need to do is add a type extension for the ExpressibleByStringLiteral protocol.

The protocol requires us to implement an initializer that receives a String. Next, we need to take this String an convert it into a CGSize. Not any String can be a CGSize. So if the value is wrong, we will crash with an error as this code will be executed by Swift during application startup. Our string format for sizes is: width,height

extension CGSize: ExpressibleByStringLiteral {
    public init(stringLiteral value: String) {
        let components = rawValue.split(separator: ",")
        guard components.count == 2,
            let width = Int(components[0]),
            let height = Int(components[1])
            else { return fatalError("Invalid Format \(value)") }
        self.init(width: size.width, height: size.height)
    }
}

Now, we can write our enum, with one downside though: The initial values have to be written as a String, since that's what the enum will use (remember, we complied with ExpressibleByStringLiteral, so that the String can be converted to our CGSize type.

enum Devices: CGSize {
   case iPhone3GS = "320,480"
   case iPhone5 = "320,568"
   case iPhone6 = "375,667"
   case iPhone6Plus = "414,736"
}

This, finally, allows us to use our CGSize enum. Keep in mind that in order to get the actual CGSize value, we have to access the rawValue of the enum.

let a = Devices.iPhone5
let b = a.rawValue
print("the phone size string is \(a), width is \(b.width), height is \(b.height)")

This works, because we explicitly told Swift that a CGSize can be created from any String.

A different option to hook into custom types it the RawRepresentable protocol, we will tackle this next.

Comparing Enums

Just like need to compare strings ("world" == "hello") or numbers you sometimes also need to compare enums. For very simple ones, like the following, this is easy as Swift takes care of it:

enum Toggle {
  case on, off
}

Toggle.on == Toggle.off

But what if you have a more complex enum with associated values like this one?

enum Character {
  case warrior(name: String, level: Int, strength: Int)
  case wizard(name: String, magic: Int, spells: [String])
}

If you'd try to compare to instances of Character Swift would complain. By default, it doesn't know how to compare enum types that have associated values. However, you can explicitly tell Swift to just compare all the values of each case and if they're the same, then the types are equal. To do that, you'd just add an empty conformance to the Equatable protocol:

enum Character: Equatable {
  case warrior(name: String, level: Int, strength: Int)
  case wizard(name: String, magic: Int, spells: [String])
}

Just this one addition Equatable will allow you to compare your types. This only works if all the values in your cases are also Equatable. This works in our example as Int, String and arrays of String are Equatable by default.

If you have a custom type that doesn't conform to Equatable, the above will not work:

struct Weapon { 
  let name: String 
}

enum Character: Equatable {
  case warrior(name: String, level: Int, strength: Int, weapon: Weapon)
  case wizard(name: String, magic: Int, spells: [String])
}

In this case, Swift will complain that Character does not conform to Equatable. So the solution here is to also conform Weapon to Equatable.

If that is not an option, you an always implement a custom Equatable conformance:

// Not Equatable Stock
struct Stock { ... }
enum Trade {
    case buy(stock: Stock, amount: Int)
    case sell(stock: Stock, amount: Int)
}
func ==(lhs: Trade, rhs: Trade) -> Bool {
   switch (lhs, rhs) {
   case let (.buy(stock1, amount1), .buy(stock2, amount2))
         where stock1 == stock2 && amount1 == amount2:
         return true
   case let (.sell(stock1, amount1), .sell(stock2, amount2))
         where stock1 == stock2 && amount1 == amount2:
         return true
   default: return false
   }
}

As you can see, we're comparing the two possible enum cases via a switch, and only if the cases match (i.e. .buy & .buy) will we compare the actual associated values.

Custom Initializers

Imagine you'd want to initialize an enum with custom data. In our example we have a Device enum that represents Apple devices and we'd like to also initialize them with non-standard names. Here's the enum:

enum Device {
  case appleWatch
}

Now if a user accidentally enters iWatch as their device, we still want to map this to the correct AppleWatch case. To do that, we will implement a custom initializer that sets self to the correct type:

enum Device { 
    case appleWatch 
    init?(term: String) {
      if term == "iWatch" {
          self = .appleWatch
      } else {
          return nil
      }
    }
}

In the above example, we used a failable initializer. However, normal initializers work just as well:

enum NumberCategory {
   case small
   case medium
   case big
   case huge

   init(number n: Int) {
        if n < 10000 { self = .small }
        else if n < 1000000 { self = .medium }
        else if n < 100000000 { self = .big }
        else { self = .huge }
   }
}

Iterating over Enum Cases

Say you've created a nice new enum with several cases:

enum Drink: String {
  case coke, beer, water, soda, lemonade, wine, vodka, gin
}

Now, you'd like to display all of those drinks at runtime in a list. You somehow want to run a for-each loop over all of your enum cases. The enum type does not offer this ability out-of-the-box. Instead, you have to explicitly tell the Swift compiler that you wish for your enum to be iterable. You do this by conforming to the empty CaseIterable protocol:

enum Drink: String, CaseIterable {
  case coke, beer, water, soda, lemonade, wine, vodka, gin
}

Now, you can easily iterate over your enum with the new allCases property:

for drink in Drink.allCases {
  print("For lunch I like to drink \(drink)\)")
}

This works only if your enum cases do not contain any associated values:

enum Drink: CaseIterable {
  case beer 
  case cocktail(ingredients: [String])
}

This code will not compile and the reason for that is simple. The Swift compiler does not know how to construct the cocktail case. And for good reason, should it be a Gin Tonic, or a Cuba libre? You wouldn't want the Swift compiler to decide that, but it has to! Because in order for you use allCases it will need to return an enum case including associated values.

So there it is, CaseIterable is a great Swift feature, however keep in mind that it can only be used with simple enum cases.

Objective-C Support

Integer-based enums such as can be bridged to Objective-c via the @objc attribute:

@objc enum Bit: Int { 
  case zero = 0 
  case one = 1
}

However once you venture away from integers (say String) or start using associated values you can't use enums from within Objective-C.

There is a manual way though. Add two methods to your enum, define a type replacement on the @objc side, and you can move enums back and forth just fine, without having to conform to private protocols:

enum Trade {
    case buy(stock: String, amount: Int)
    case sell(stock: String, amount: Int)
}

// This type could also exist in Objective-C code.
@objc class ObjcTrade: NSObject {
    var type: Int
    var stock: String
    var amount: Int
    init(type: Int, stock: String, amount: Int) {
        self.type = type
        self.stock = stock
        self.amount = amount
    }
}

extension Trade  {

    func toObjc() -> ObjcTrade {
        switch self {
        case let .buy(stock, amount):
            return ObjcTrade(type: 0, stock: stock, amount: amount)
        case let .sell(stock, amount):
            return ObjcTrade(type: 1, stock: stock, amount: amount)
        }
    }

    static func fromObjc(source: ObjcTrade) -> Trade? {
        switch (source.type) {
        case 0: return Trade.buy(stock: source.stock, amount: source.amount)
        case 1: return Trade.sell(stock: source.stock, amount: source.amount)
        default: return nil
        }
    }
}

This still has the downside that you need to mirror your enum via an NSObject based type on the Objective-C side (or you could just go and use an NSDictionary), but if you ever end up in a situation where you need to access an enum with associated values from Objective-C, this is a way to do it.

Enums in the Standard Library

Before we go on and explore various use cases for enums in your projects, it might be tempting to see some of the enums being used in the Swift standard library, so let's have a look.

Bit The Bit enum can have two possible values, One, and Zero. It is used as the Index type for CollectionOfOne<T>.

FloatingPointClassification This enum defines the set of possible IEEE 754 "classes", like NegativeInfinity, PositiveZero, or SignalingNaN.

Mirror.AncestorRepresentation, and Mirror.DisplayStyle These two are used in the context of the Swift Reflection API.

Optional Not much to say here

Process The Process enum contains the command line arguments of the current process (Process.argc, Process.arguments). This is a particularly interesting enum as it used to be a struct in Swift 1.0.

Practical Usecases

We've already seen a couple of useful enums in the previous feature descriptions. Examples would be Optional, Either, FileNode, or the binary tree. However, there're many more scenarios where using an enum wins over a struct or class. Usually, if your problem domain can be divided into a finite set of distinctive categories, an enum may be the right choice. Even only two cases are a perfectly valid scenario for an enum, as the Optional and Either types show.

Here, then, are some more examples of practical enum usage to fuel your creativity.

Error Handling

One of the prime examples of Enum usage in Swift is, of course, the new error handling in Swift 2.0. Your throwing function can throw anything which conforms to the empty ErrorType protocol. As the Swift documentation succinctly observes:

Swift enumerations are particularly well suited to modeling a group of related error conditions, with associated values allowing for additional information about the nature of an error to be communicated.

As an example, have a look at the popular JSON Decoding library Argo. When their JSON Decoding fails, it can fail due to two primary reasons.

  1. The JSON Data lacks a key which the end model requires (say your model has a property username and somehow the JSON lacks that)
  2. There's a type mismatch. Say instead of a String the username property in the JSON contains an NSNull 1.

In addition to that, Argo also includes a custom error for anything not fitting in these two categories above. Their ErrorType enum looks like this:

enum DecodeError: Error {
  case typeMismatch(expected: String, actual: String)
  case missingKey(String)
  case custom(String)
}

All cases have associated values that contain additional information about the error in question.

A more general ErrorType for complete HTTP / REST API handling could look like this:

enum APIError : Error {
    // Can't connect to the server (maybe offline?)
    case connectionError(error: NSError)
    // The server responded with a non 200 status code
    case serverError(statusCode: Int, error: NSError)
    // We got no data (0 bytes) back from the server
    case noDataError
    // The server response can't be converted from JSON to a Dictionary
    case JSONSerializationError(error: Error)
    // The Argo decoding Failed
    case JSONMappingError(converstionError: DecodeError)
}

This ErrorType implements the complete REST Stack up to the point where your app would get the completely decoded native struct or class object.

More information on ErrorType and more enum examples in this context can be found in the official documentation here.

Observer Pattern

There're various ways of modelling observation in Swift. If you include @objc compatibility, you can use NotificationCenter or KVO. Even if not, the didSet syntax makes it easy to implement simple observation. Enums can be used here in order to make the type of change that happens to the observed object clearer. Imagine collection observation. If we think about it, we only have a couple of possible cases: One or more items are inserted, one or more items are deleted, one or more items are updated. This sounds like a job for an enum:

enum Change {
     case insertion(items: [Item])
     case deletion(items: [Item])
     case update(items: [Item])
}

Then, the observing object can receive the concrete information of what happened in a very clean way. This could easily be extended by adding oldValue and newValue, too.

Status Codes

If you're working with an outside system which uses status codes (or error codes) to convey information, like HTTP Status Codes, enums are obviously a great way to encode the information. 1

enum HttpError: String {
  case code400 = "Bad Request"
  case code401 = "Unauthorized"
  case code402 = "Payment Required"
  case code403 = "Forbidden"
  case code404 = "Not Found"
}

Result Types

Enums are also frequently used to map the result of JSON parsing into the Swift type system. Here's a short example of this:

enum JSON {
    case JSONString(Swift.String)
    case JSONNumber(Double)
    case JSONObject([String : JSONValue])
    case JSONArray([JSONValue])
    case JSONBool(Bool)
    case JSONNull
}

Similarly, if you're parsing something else, you may use the very same structure to convert your parsing results into Swift types. This also makes perfect sense to only do it during the parsing / processing step and then taking the JSON enum representation and converting it into one of your application's internal class or struct types.

UIKit Identifiers

Enums can be used to map reuse identifiers or storyboard identifiers from stringly typed information to something the type checker can understand. Imagine a UITableView with different prototype cells:

enum CellType: String {
    case buttonValueCell = "ButtonValueCell"
    case unitEditCell = "UnitEditCell"
    case labelCell = "LabelCell"
    case resultLabelCell = "ResultLabelCell"
}

Units

Units and unit conversion are another nice use case for enums. You can map the units and their respective values and then add methods to do automatic conversions. Here's an oversimplified example.

enum Liquid: Float {
  case ml = 1.0
  case l = 1000.0
  func convert(amount: Float, to: Liquid) -> Float {
      if self.rawValue < to.rawValue {
         return (self.rawValue / to.rawValue) * amount
      } else {
         return (self.rawValue * to.rawValue) * amount
      }
  }
}

Another example of this would be Currency conversion. Also, mathematical symbols (such as degrees vs radians) can benefit from this.

Games

Enums are a great use case for games, where many entities on screen belong to a specific family of items (enemies, obstacles, textures, ...). In comparison to native iOS or Mac apps, games oftentimes are a tabula rasa. Meaning you invent a new world with new relationships and new kinds of objects, whereas on iOS or OSX you're using a well-defined world of UIButtons, UITableViews, UITableViewCells or NSStackView.

What's more, since Enums can conform to protocols, you can utilize protocol extensions and protocol based programming to add functionality to the various enums that you defined for your game. Here's a short example that tries to display such a hierarchy:

enum FlyingBeast { case dragon, hippogriff, gargoyle }
enum Horde { case ork, troll }
enum Player { case mage, warrior, barbarian }
enum NPC { case vendor, blacksmith }
enum Element { case tree, fence, stone }

protocol Hurtable {}
protocol Killable {}
protocol Flying {}
protocol Attacking {}
protocol Obstacle {}

extension FlyingBeast: Hurtable, Killable, Flying, Attacking {}
extension Horde: Hurtable, Killable, Attacking {}
extension Player: Hurtable, Obstacle {}
extension NPC: Hurtable {}
extension Element: Obstacle {}

Stringly Typed Code

In bigger Xcode projects, you're quickly accumulating lots of resources which are accessed by string. We've already mentioned reuse identifiers and storyboard identifiers above, but there's also: Images, Segues, Nibs, Fonts, and other resources. Oftentimes, those resources can be grouped into several distinct sets. If that's the case, a String typed enum is a good way of having the compiler check this for you.

enum DetailViewImages: String {
  case background = "bg1.png"
  case sidebar = "sbg.png"
  case actionButton1 = "btn1_1.png"
  case actionButton2 = "btn2_1.png"
}

For iOS users, there's also R.swift which auto generates structs for most of those use cases. Sometimes you may need more control though (or you may be on a Mac 1)

API Endpoints

Rest APIs are a great use case for enums. They're naturally grouped, they're limited to a finite set of APIs, and they may have additional query or named parameters which can be modelled through associated values.

Take, for example, a look at a simplified version of the Instagram API

enum Instagram {
  enum Media {
    case popular
    case shortcode(id: String)
    case search(lat: Float, 
                min_timestamp: Int, 
                lng: Float, 
                max_timestamp: Int, 
                distance: Int)
  }
  enum Users {
    case user(id: String)
    case feed
    case recent(id: String)
  }
}

Ash Furrow's Moya library is based around this idea of using enums to map rest endpoints.

Linked Lists

Airspeed Velocity has a great writeup on how to implement a Linked List with an enum. Most of the code in his post goes far beyond enums and touches a lot of interesting topics 1, but the basis of his linked list looks kinda like this (I simplified it a bit):

enum List {
    case end
    indirect case node(Int, next: List)
}

Each node case points to the next case, and by using an enum instead of something else, you don't have to use an optional for the next value to signify the termination of the list.

Airspeed Velocity also wrote a great post about the implementation of a red black tree with indirect Swift enums, so while you're already reading his blog, you may just as well also read this one.

Settings

This is a very, very smart solution that Erica Sadun came up with. Basically whenever you'd use a dictionary of attribute keys as a way to configure an item, you'd instead use a Set of enums with associated values. That way, the type checker can confirm that your configuration values are of the correct type.

For more details, and proper examples, check out her original blog post.

Limitations

Tuples

The biggest issue is, Tuple support. I love tuples, they make many things easier, but they're currently under-documented and cannot be used in many scenarios. In terms of enums, you can't have tuples as the enum value:

enum Devices: (intro: Int, name: String) {
  case iPhone = (intro: 2007, name: "iPhone")
  case appleTV = (intro: 2006, name: "Apple TV")
  case appleWatch = (intro: 2014, name: "Apple Watch")
}

This may not look like the best example, but once you start using enums, you'll often end up in situations where you'd like to be able to do something like the above.

Default Associated Values

Another thing which you may run into is that associated values are always types but you can't set a default value for those types. Imagine such an example:

enum Characters {
  case mage(health: Int = 70, magic: Int = 100, strength: Int = 30)
  case warrior(health: Int = 100, magic: Int = 0, strength: Int = 100)
  case neophyte(health: Int = 50, magic: Int = 20, strength: Int = 80)
}

You could still create new cases with different values, but the default settings for your character would be mapped.

Similar Articles