Enumerations
Enums in Swift are the first-class types. They adopt many features that are supported by classes only such as Computed properties, instance methods, etc and they can also define the initializer to provide an initial value that enum represents. They are of the value type.
According to Apple docs:
An enumeration defines a common type for a group of related values and enables you to work with those values in a type-safe way within your code.
Enumeration Syntax
We can create enumeration with enum
keyword and place its entire definition within the curly brace:
1 2 3 |
enum enumName { // enum definition goes here } |
example with type:
1 2 3 4 5 6 7 |
enum ProductTpye { case virtual case bundle case downloadable case grouped case simple } |
We can also write multiple cases in a single line, separated by commas:
1 2 3 |
enum Gender { case male, female } |
We can now set a variable to an enum case like so:
1 |
var productType = ProductType.virtual |
or
1 |
var productType: ProductType = .virtual |
Matching Enumeration Values with a switch statement:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
switch productType { case .bundle: print("This is a bundle product") case .downloadable print("This is a downloadable product") case .grouped print("This is a grouped product") case .simple print("This is a simple product") case .virtual print("This is a virtual product") } // This is a bundle product |
Iterating over Enumeration Cases
We can enable iteration over enum by implementing CaseIterable protocol.
1 2 3 4 5 6 |
enum Beverage: CaseIterable { case coffee, tea, juice } let numberOfChoices = Beverage.allCases.count print("\(numberOfChoices) beverages available") // Prints "3 beverages available" |
Associated Values
It helps us to store some value of other types alongside our swift case values. These values vary each time we use that case as a value in your code. Enumerations similar to these are known as discriminated unions, tagged unions, or variants in other programming languages.
1 2 3 4 |
enum Barcode { case upc(Int, Int, Int, Int) case qrCode(String) } |
This can be read as:
“Define an enumeration type called Barcode
, which can take either a value of upc
with an associated value of type (Int
, Int
, Int
, Int
), or value of qrCode
with an associated value of type String
.”
You can then create new barcodes using either type:
1 |
var productBarcode = Barcode.upc(8, 85909, 51226, 3) |
This example creates a new variable called productBarcode
and assigns it a value of Barcode.upc
with an associated tuple value of (8, 85909, 51226, 3)
.
You can assign the same product a different type of barcode:
1 |
productBarcode = .qrCode("ABCDEFGHIJKLMNOP") |
You can also check different bar code types using a switch statement.
1 2 3 4 5 6 7 |
switch productBarcode { case .upc(let numberSystem, let manufacturer, let product, let check): print("UPC: \(numberSystem), \(manufacturer), \(product), \(check).") case .qrCode(let productCode): print("QR code: \(productCode).") } // Prints "QR code: ABCDEFGHIJKLMNOP." |
Raw Values
Raw values can be strings, characters, or any of the integer or floating-point number types. Each raw value must be unique within its enumeration declaration.
According to the Apple docs:
Raw values are not the same as associated values. Raw values are set to prepopulated values when you first define the enumeration in your code, like the three ASCII codes above. The raw value for a particular enumeration case is always the same. Associated values are set when you create a new constant or variable based on one of the enumeration’s cases, and can be different each time you do so.
Implicitly Assigned Raw Values
When you’re working with enumerations that store integer or string raw values, you don’t have to explicitly assign a raw value for each case. When you don’t, Swift automatically assigns the values for you.
1 2 3 |
enum Planet: Int { case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune } |
In the example above, Planet.mercury
has an explicit raw value of 1
, Planet.venus
has an implicit raw value of 2
, and so on.
When strings are used for raw values, the implicit value for each case is the text of that case’s name.
1 2 3 |
enum CompassPoint: String { case north, south, east, west } |
Initializing from a Raw Value
If you define an enumeration with a raw-value type, the enumeration automatically receives an initializer that takes a value of the raw value’s type (as a parameter called rawValue
) and returns either an enumeration case or nil
. You can use this initializer to try to create a new instance of the enumeration.
1 2 |
let possiblePlanet = Planet(rawValue: 7) // possiblePlanet is of type Planet? and equals Planet.uranus |
Enums and Equatable
Swift 4.1 made it easier to compare enums. Nothing more needed then just defining your enum.
1 2 3 4 5 6 7 |
let productOne = ProductType.virtual if productOne == .downloadable { print("Same") } else { print("Different") } |
This does not work for enums with values. In that case, you have to inherit from the Equatable
protocol.
1 2 3 4 5 6 7 8 9 10 11 12 |
enum TimeInterval: Equatable { case seconds(Int) case milliseconds(Int) case microseconds(Int) case nanoseconds(Int) } if TimeInterval.seconds(1) == .seconds(2) { print("Matching!") } else { print("Not matching!") } |
Enums with methods:
1 2 3 4 5 6 7 8 |
enum CompassPoint: String { case north, south, east, west func currDir() -> String { return self.rawValue } } print(CompassPoint.north.currDir()) // north |
Recursive Enumerations
A recursive enum is an enum that has another instance of the enum as the associated value for one or more of the enumeration cases. You indicate that an enumeration case is recursive by writing indirect
before it, which tells the compiler to insert the necessary layer of indirection.
For example, here is an enumeration that stores simple arithmetic expressions:
1 2 3 4 5 |
enum ArithmeticExpression { case number(Int) indirect case addition(ArithmeticExpression, ArithmeticExpression) indirect case multiplication(ArithmeticExpression, ArithmeticExpression) } |
You can also write indirect
before the beginning of the enumeration to enable indirection for all of the enumeration’s cases that have an associated value:
1 2 3 4 5 |
indirect enum ArithmeticExpression { case number(Int) case addition(ArithmeticExpression, ArithmeticExpression) case multiplication(ArithmeticExpression, ArithmeticExpression) } |
This enumeration can store three kinds of arithmetic expressions: a plain number, the addition of two expressions, and the multiplication of two expressions. The addition
and multiplication
cases have associated values that are also arithmetic expressions—these associated values make it possible to nest expressions
The code below shows the ArithmeticExpression
recursive enumeration being created for (5 + 4) * 2
:
1 2 3 4 |
let five = ArithmeticExpression.number(5) let four = ArithmeticExpression.number(4) let sum = ArithmeticExpression.addition(five, four) let product = ArithmeticExpression.multiplication(sum, ArithmeticExpression.number(2)) |
A recursive function is a straightforward way to work with data that has a recursive structure. For example, here’s a function that evaluates an arithmetic expression:
1 2 3 4 5 6 7 8 9 10 11 12 |
func evaluate(_ expression: ArithmeticExpression) -> Int { switch expression { case let .number(value): return value case let .addition(left, right): return evaluate(left) + evaluate(right) case let .multiplication(left, right): return evaluate(left) * evaluate(right) } } print(evaluate(product)) // Prints "18" |
This function evaluates a plain number by simply returning the associated value. It evaluates an addition or multiplication by evaluating the expression on the left-hand side, evaluating the expression on the right-hand side, and then adding them or multiplying them.