Enumerations in Swift

In our last post, Swift for Ruby Devs: The Basics pt. 2, we compared the differences in syntax between Swift and Ruby of basic data structures such as control flow statements, functions, classes, and structs. We also examined some unique features that these types have in Swift that are not familiar to us in Ruby. With this knowledge, we can start learning about new concepts and data types that are integral to the Swift programming language and really make it stand out.


Enumerations


Enums are first-class data types that allow you to group a finite set of related values together into a single type. Enums can be useful when we want to represent the days of the week, the three colors on a traffic light, HTTP protocols, cardinal directions, or the values of a dropdown menu on our app. They provide a convenient way to encapsulate these common values into a single construct that can be used in a type-safe manner by allowing the compiler to check we are using the values correctly. Each value is known as a "member value" or "enumeration case".

Enums are created using the enum keyword and each member value or enumeration case is defined using the case keyword. Here's an example of an enum that represents HTTP methods.

enum HTTPMethod {  
  case get
  case put
  case post
  case delete
}


Multiple cases can also be defined on a single line.

enum HTTPMethod {  
  case get, put, post, delete
}


We can initialize an instance of HTTPMethod using dot notation.

let httpMethod = HTTPMethod.put  


The type of httpMethod is inferred when it is initialized with a HTTPMethod. Since the type of httpMethod is now known, we can set it to another HTTPMethod using a shorter dot syntax.

httpMethod = .post  


Raw Values

Cases in an enum can contain default values known as raw values. Raw values must all be of the same type and they can only be one of four types: String, Character, Int, or Double. We must specify the type of the raw values when defining the enum. Consider an enum that represents HTTP status codes and has raw values of type Int.

enum HTTPStatusCode: Int {  
    case Continue = 100
    case Success = 200
    case Unauthorized = 401
    case NotFound = 404
}


We can access the raw value of an enum with the rawValue property.

HTTPStatusCode.Success.rawValue  
  => 200


In the case of HTTPMethod, if we were to specify that the enum includes raw values of type String but don't actually specify any raw values, Swift will provide default raw values that are the string representation of the enumeration case.

enum HTTPMethod: String {  
  case get, put, post, delete
}

HTTPMethod.delete  
  => "delete"


Associated Values

Enumeration cases are defined types in their own right that can be stored into variables or constants and we can then check for these types in our code. There are some instances, however, where we need to store additional information associated with each case. This custom information or associated values can be of any type and the value types can be different for each case. Here's an example of an enum representing vehicles with associated values.

enum VehicleType {  
  case car(make: String, model: String, year: Int)
  case pickupTruck(make: String, model: String, year: Int)
  case bus(String, String, Int)
  case semiTruck(make: String, model: String, year: Int, numberOfWheels: Int)
}


VehicleType includes a car and pickupTruck that have associated values of make, model, and year. The bus case also includes the same types but it's associated values aren't named like in car or pickupTruck. This is simply to demonstrate that it isn't necessary to name associated values and we'll see in a later example how to extract these associated values into a constant or variable. Finally, semiTruck contains the same associated values as car and pickupTruck and one additional value numberOfWheels. Here's how you'd instantiate an instance of a car or bus.

VehicleType.car(make: "Porsche", model: "718 Cayman", year: 2017)

VehicleType.bus("Ford", "Transit Shuttle MRTransit", 2017)  


The different vehicle types can be checked using a switch statement and the associated values can be extracted out into constants or variables for use.

let myCar = VehicleType.car(make: "Porsche", model: "718 Cayman", year: 2017)

switch myCar {  
case let .car(make, model, year):  
  print("Car: \(make) \(model) \(year)")
case let .pickupTruck(make, model, year):  
  print("Pickup: \(make) \(model) \(year)")
case let .bus(make, model, year):  
  print("Bus: \(make) \(model) \(year)")
case let .semiTruck(make, model, year, numberOfWheels):  
  print("Semi Truck: \(make) \(model) \(year) \(numberOfWheels) wheels")
}


In this example, I've decided to store all associated values in constants by using the case let shorthand. Alternatively, we can add the let keyword before each associated value name but that is a lot more verbose and doesn't necessarily improve readability. We could have also stored all the associated values into variables with the var keyword. Lastly, if you need to store some associated values in constants and others in variables, we can do that too.

switch myCar {  
case .car(let make, let model, var year)  
...
}


You may have noticed in the first switch statement above, that there is no default case. Swift requires switch statements to include a default case when it is not able to determine every possible matching pattern against the value being considered. Because enums have a finite set of enumeration cases, if we include all these cases in a switch statement, the compiler is able to determine that it is exhaustive and thus does not require a default case.

Initializers and Functions

Enums can define optional initializer methods that do some work on initialization, such as, providing an initial case value. This can be useful when we have an enum representing the days of the week and want to always initialize an instance on the first day of the week.

enum Day: String {  
  case monday, tuesday, wednesday, thursday, friday, saturday, sunday

  init() {
    self = .monday
  }
}

let firstDayOfWeek = Day()

firstDayOfWeek.rawValue  
  => "monday"


Functions can be defined in enums as well to provide some functionality related to the value of the enum instance. Suppose we gave the Day enum raw values of type Int, and we wanted a function that'll determine the number of days remaining until the weekend.

enum Day: Int {  
  case monday = 1, tuesday, wednesday, thursday, friday, saturday, sunday

  func numberOfDaysTilWeekend() -> Int {
   return Day.saturday.rawValue - self.rawValue
  }
}

Day.tuesday.numberOfDaysTilWeekend()  
  => 4


One thing to note about the example above is that we only explicitly defined a raw value for monday, yet tuesday and saturday seem to have raw values defined. Since we've defined raw values to be of type Int, Swift automatically sets raw values for all other cases by auto-incrementing each raw value. All we have to do is set an initial value for monday. Allowing Swift to infer the raw values for us makes for less code we have to write, though it might do so at the cost of readability. We can also provide raw Int values for each case if it'll make our code easier to understand.


Summary


Enums in Swift are very powerful and can be used in a lot of different ways, allowing us to work with our code in a type-safe manner. Enums also have some other unique features such as computed properties to provide information about an enum's current value, and recursive enumeration cases for more complex behaviors that are not discussed here. To learn more about these complex features of enums, take a look at the Swift Language Guide.