LEARN SWIFT

Buy the PDF

Chapter 14 Enumerations

You can use an enumeration (or enum) to define a set of predefined values. This is convenient and can improve the readability of your code when something only takes on a limited set of values. Rather than modelling it with more general type such as an integer or string, we can model it with an enumeration.

14.1 Defining and using an Enumeration

Enumerations are a first-class type in Swift. You can use an enumeration anywhere that Swift expects a type.

You declare an enumeration using the enum keyword followed by a list of the individual members of the enumerations.

Here’s an example that defines an enumeration. Let’s say we want to represent a users currency. We could represent it as an integer, where 0 represents dollars and 1 represents euros. Or we could represent it as a string with 2 possible values “dollar” or “euro”. But since there is fixed number of possible values for currency we can represent it most naturally using an enumeration.

The following example defines an enumeration called Currency that has 2 members: Dollar and Euro.

 1 enum Currency {
 2     case Dollar
 3     case Euro
 4 }
 5 
 6 class User {
 7     var currency:Currency
 8 
 9     init(currency:Currency){
10         self.currency = currency
11     }
12 }
13 
14 var joe = User(currency: .Dollar)
15 var jane = User(currency: .Euro)
16 
17 joe.currency == .Dollar // true
18 joe.currency == Currency.Dollar // true

We also define a User class that uses the Currency enumeration to store the users currency. It has an initializer that takes a Currency as a parameter. We can use members of the enumeration by using a dot followed by the enumeration member name (Note: if you prefer you can also use the longer version that gives the full enumeration name and member name. I.e. you can use either Currency.Dollar or .Dollar). For example, at line 14, we create a new user that has a dollar currency by using the initializer and passing .Dollar as the currency.

We can check if something matches a specific enumeration member using == (lines 17,18)

In addition to being able to define a number of members of an enumeration they also share many of the same capabilities that classes and structures have. These include:

  • properties. Enumerations can have computed properties, but not stored properties.
  • instance methods.
  • type methods (using the static keyword).
  • initializers.
  • extensions.
  • protocols.

These follow the same syntax as the equivalent for classes and structures. So I won’t go into detail about the syntax for each of them with respect to enumerations. You can refer back to chapter 12 for details.

Here’s an example that adds an instance method to our Currency enum to convert it to a string.

enum Currency {
    case Dollar
    case Euro

    func toString()-> String {
        switch self{
        case .Dollar:
            return "dollar"
        case .Euro:
            return "euro"
        }
    }
}

var joe = User(currency: .Dollar)
joe.currency.toString()  // "dollar"

14.2 Raw values

Enumerations can have raw-values associated with each member. Raw values can’t change. For the example above, our enumeration doesn’t have raw values. If we want to have raw values in our enumeration, we declare type of the raw value when declaring the enumeration and assign the raw value to each member of the enumeration.

Here’s an example that models a deck of cards.

 1 enum Suit:String {
 2     case Heart = "♥️"
 3     case Diamond = "♦️"
 4     case Spade = "♠️"
 5     case Club = "♣️"
 6 }
 7 
 8 enum Rank: Int {
 9     case Two = 2
10     case Three, Four, Five, Six, Seven, Eight, Nine, Ten
11     case Jack
12     case Queen
13     case King
14     case Ace
15 }
16 
17 let ace = Rank.Ace
18 ace.rawValue         // 14
19 Rank.Two.rawValue    // 2
20 Rank.Four.rawValue   // 4
21 Rank.King.rawValue   // 13
22 
23 let heart = Suit.Heart
24 heart.rawValue       // "♥️"
25 Suit.Diamond.rawValue // "♦️"

In this example we declare 2 different enumerations that we use for representing cards - one for the rank and one for the suit. At line 1 we declare our Suit enumeration, which has raw values of type String. Then when declaring each member we also have to assign it a raw string value, e.g. case Heart = "♥️".

When we want to access the raw value for an enumeration member, we can use the .rawValue method (e.g. lines 18-21)

Our second enumeration, Rank is for representing the rank of a card (line 8). This one has integer values as its raw value type. With an integer value we have the option of only assigning the raw value to the first member of the enumeration. Swift will then automatically assign increasing integers to each subsequent member of the enumeration. For example, in our Rank enumeration we declare that Rank Two has raw value of 2. We don’t assign raw values to any of the other members. As a result, Swift assigns them auto-incremented integer values. So Rank.Three has raw value of 3, Rank.Four has raw value of 4, Rank.Ace has raw value of 14 etc.

These raw values can’t change - they are part of the definition of the enumeration.

Once you’ve defined an enumeration from raw values you can also initialize items using a raw value by initializing them using the rawValue key.

let k = Rank(rawValue: 13)
k == Rank.King // true

14.3 Associated values

Associated values allow us to store data with a member of an enumeration. These can change between different instances of an enumeration. Associated values let you associate a tuple containing values with the member of an enumerations. Each member can have a different type of tuple associated with it to store values.

Lets say we have a computation that returns a result and we want to represent the result with an enumeration. When the computation is successful we just want to store the integer but when there’s an error message we want to store that error message too. We could do this using an enumeration.

enum Result {
    case Success(Int)
    case Error(Int, String)
}
let succ = Result.Success(3)
let err = Result.Error(-1, "Oh no, It's an error")

Here we see how to store associated values with different members of an enumeration. This enumeration has 2 members. Success has an associated value that is a tuple containing a single integer. Error has an associated value that is a tuple with 2 elements - an integer and a string.

We’ll see in the next section how to process associated values using a switch statement.

14.3.1 Associated values vs raw values

Raw values are fixed at the time that you define the enumeration. They are for storing something that is a value representation of the enumeration member.

Associated values OTOH are for storing data related to individual instances of an enumeration. If members of your enumeration can have different data associated with different instances you can use associated values to store that data.

14.4 Pattern matching with switch

We can do pattern matching against an enumeration with a switch statement.

switch joe.currency{
case .Dollar:
    println("Joe uses $$$")
case .Euro:
    println("Joe uses €€€")
}                    // prints "Joe uses $$$"

Here we see how we can use a switch statement to branch on an enumeration. The members of the enumeration can be used to match cases in the switch statement. Note that branches of a switch statement must be exhaustive, so you should either have cases that cover all members of the enumeration or else have a default clause.

We can also use switch to pattern match against the associated values in an enumeration. When the enumeration has associated value, we can extract those values in each branch of the switch statement. Here we continue our example from the previous section. We use let in the individual branches of our switch statement to store the associated values for the enumerations so that we can access them within that branch.

enum Result {
    case Success(Int)
    case Error(Int, String)
}
let succ = Result.Success(3)
let err = Result.Error(-1, "Oh no, It's an error")

func handleResult(result: Result){
    switch result{
    case .Success(let answer):
        println("Success: The answer is \(answer)")
    case .Error(_, let message):
        println("Error: \(message)")
    }
}

handleResult(succ)
handleResult(err)

Our handleResult function here takes a Result enumeration and processes it using a switch statement. For example, when we call handleResult(err) here, it matches the .Error branch of the switch statement. The second associated value is assigned to the local constant answer (we don’t care about the first associated here since we don’t use it. We use _ to ignore the first associated value). We can then access the associated values using the assigned constant within that case statement.