Swift Enums and Pattern Matching
Enums in Swift let you model a fixed set of related cases, and switch statements pair with them to guarantee every case is handled.
Defining a Basic Enum
An enum groups related values under one type, where each possible value is called a case. Unlike enums in many other languages, Swift enums are first-class types that can have methods, computed properties, and even protocol conformances. A switch statement over an enum must be exhaustive, covering every case (or providing a default), which means the compiler will flag you the moment you add a new case somewhere and forget to handle it elsewhere.
A basic enum with switch
enum Direction {
case north, south, east, west
}
let heading = Direction.north
switch heading {
case .north:
print("Heading north")
case .south:
print("Heading south")
case .east, .west:
print("Heading east or west")
}Enums with Raw Values
Sometimes each case naturally maps to an underlying value, like an HTTP status code or an ordinal position. Declaring an enum as Int, String, or another RawRepresentable type lets every case carry a constant raw value, accessible through .rawValue. You can also go the other direction, constructing a case from its raw value using a failable initializer that returns an optional.
Raw value enums
enum Planet: Int {
case mercury = 1, venus, earth, mars
}
let earthOrder = Planet.earth.rawValue // 3
if let planet = Planet(rawValue: 4) {
print("Found \(planet)") // Found mars
}Associated Values
Raw values are fixed and identical for every instance of a case, but associated values let each instance of a case carry its own unique data — a completely different capability. This is how Swift models things like a network response that is either a success carrying data, or a failure carrying an error code and message. Pattern matching in switch can bind those associated values to local constants, and a where clause can further filter which pattern matches.
Associated values with pattern matching
enum NetworkResponse {
case success(data: String)
case failure(code: Int, message: String)
case loading
}
func handle(_ response: NetworkResponse) {
switch response {
case .success(let data):
print("Received: \(data)")
case .failure(let code, let message) where code >= 500:
print("Server error \(code): \(message)")
case .failure(let code, let message):
print("Error \(code): \(message)")
case .loading:
print("Still loading...")
}
}
handle(.failure(code: 503, message: "Service unavailable"))- where clauses narrow a case pattern with an extra boolean condition
- let/var bindings inside a pattern extract associated or tuple values
- Tuple patterns let a single switch match on several values at once
- Range patterns (1...5, ..<10) match against a range of values
- The underscore _ ignores a value you don't care about in a pattern
Exercise: Swift Enums
Are Swift enums value types or reference types?