Enumerations in Swift
Kate
Posted on October 12, 2020
Let’s talk about one of my favorite things in Swift - enumerations (or enums for short). An enumeration allows you to create a specific and limited set of associated values. They can be applied readily in a situation where an element will be limited by a strict set of options, and are extremely useful for handling exhaustiveness.
An enum is a first-class type, meaning they can take on functions and features that apply specifically to the class type, such as computed properties and instance methods(more on the specifics of class, structs, and enums here).
Enums are declared with the enum keyword, followed by a capitalized name, then a list of cases, which act as the possible enum options, inside. Cases can be listed on separate lines, each with the case keyword in front, or all on one line separated by commas. Let’s use an enum that lists possible types of pet available in a pet store.
enum Animal {
case cat
case dog
case rabbit
case hamster
case snake
case turtle
case fish
case bird
}
Or
enum Animal {
case cat, dog, rabbit, hamster, snake, turtle, fish, bird
}
Either format is acceptable, but longer case lists might be easier to read through listed out as case statements on separate lines, as opposed to on one line.
To declare an value of an enum type you can assign it directly
var thatKittyInTheWindow = Animal.cat
Or assign the type, then specify the case with dot syntax. Listing the specific enum again is unnecessary because we already know which one the value will use.
var thatKittyInTheWindow: Animal = .cat
The specificity of the enumeration is one of its advantages, because the listed cases pair perfectly with a switch statement. A switch statement will ‘switch’ (essentially ‘choose an option’) based on which case is handed to it, then take the appropriate action you have coded in.
All options have to be covered in the switch statement, because it is by definition exhaustive. An option must be present for every possible case. If an option is not specified, the compiler will complain. If you want some options to produce the same result, you can group them, or you can include a default statement, which will be used as a fallback if a case has been left out of the switch. Let’s see an example using sounds these animals might make.
switch Animal {
case .cat:
print(“Meow”)
case .dog:
print(“Woof”)
case .rabbit, .hamster:
print(“Squeak”)
case .snake:
print(“Hisss”)
case .bird:
print(“Tweet”)
default:
print(“…”)
}
Here, the .turtle and .fish cases would trip the default statement, while the .rabbit and .hamster cases have been placed together and will result in the same print statement.
An enum can also have raw values associated with each case. These raw values are basic types only, such as strings, characters, and number types like ints and doubles. You add a raw value to an enum by adding a colon then the type after the enum name.
enum Animal: String {
case cat
case dog
case rabbit
case hamster
case snake
case turtle
case fish
case bird
}
Here, we’ve assigned a raw string value to the enum. By default, this will produce a raw value for each case that is a string version of the case keyword. Accessing the raw value for Animal.cat, for example, will produce “cat”, so value below will be “cat”.
var thatKittyInTheWindow: Animal = .cat
let value = thatKittyInTheWindow.rawValue
Assigning an Int as a raw value will automatically assign an integer value to each case, based on zero indexing. This also works if you specify the index for the first case item - the rest will receive an index automatically.
enum Animal: Int {
case cat = 1
case dog
case rabbit
case hamster
case snake
case turtle
case fish
case bird
}
Here, dog would have a raw value of 2, rabbit 3, and so on. You can, of course, always specify each raw value to be something particular too, rather than letting the compiler handle it automatically. Perhaps here we would assign pet shop prices to each animal via raw value, or give them names.
enum Animal: String {
case cat = “Socks”
case dog = “Buddy”
case rabbit = “Thumper”
// and so on
}
As long as the raw value is one of the basic accepted types it can be assigned. Once set, however, the raw value for any enum case is always the same. The situation above would only be practical if the store had one of each type of pet, since if they had multiple, the same name would apply across for each dog, etc.
This problem can be remedied with associated values, however. An associated value is ‘associated’ with an enum case, but is specified only as a type. The actual value is set upon declaring a type of that enum case. This way, we can associate a string with each animal, which will be its name, but we will not have to assign a name upon declaration of the enum (like with raw values).
enum Animal {
case cat(String)
case dog(String)
case rabbit(String)
case hamster(String)
case snake(String)
case turtle(String)
case fish(String)
case bird(String)
}
Now we can ensure a given animal has a proper name, set separately when we declare a value of the Animal type.
let thatKittyInTheWindow: Animal = .cat(“Tiger”)
let thatPuppyInTheWindow: Animal = .dog(“Lily”)
These associated values can then be used in a switch statement as well.
switch Animal {
case .cat(let name):
print(“Kitty’s name is \(name)”)
case .dog(let name):
print(“Doggie’s name is \(name)”)
// and so on
}
Here we assign the associated value to constant, then use string interpolation to add it to a print statement.
As you can see, there are a lot of possibilities for ways to use enums to your advantage, especially by pairing them with switch statements for exhaustive and easy to read code. There are even more uses out there - this short lesson only covered the basics, but the docs contain even more specific details if you want to delve into more.
Posted on October 12, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.