Enumerations
Enumerations (enum
) define a finite set of related values, supporting raw values, associated values, and recursive definitions. They are powerful for modeling state and data variants.
Basic Enumerations
Define with enum
, where cases represent possible values.
Example:
enum Direction {
case north
case south
case east
case west
}
let heading = Direction.north
switch heading {
case .north: print("Going north")
default: print("Other direction")
}
Raw Values
Assign raw values (e.g., Int
, String
) to cases, automatically incrementing for integers.
Example: String Raw Values:
enum Planet: String {
case mercury = "Mercury"
case venus = "Venus"
case earth = "Earth"
}
let planet = Planet(rawValue: "Earth") // .earth
print(planet?.rawValue ?? "Unknown") // "Earth"
Example: Integer Raw Values:
enum StatusCode: Int {
case ok = 200
case notFound = 404
case serverError = 500
}
let code = StatusCode(rawValue: 404) // .notFound
print(code?.rawValue) // 404
Associated Values
Store additional data with cases, enabling flexible data modeling.
Example:
enum Barcode {
case upc(Int, Int, Int, Int)
case qrCode(String)
}
let barcode = Barcode.upc(123, 456, 789, 0)
switch barcode {
case let .upc(a, b, c, d):
print("UPC: \(a)-\(b)-\(c)-\(d)")
case let .qrCode(content):
print("QR: \(content)")
}
Recursive Enumerations
Use indirect
for cases that reference the enum itself.
Example:
indirect enum ArithmeticExpression {
case number(Int)
case addition(ArithmeticExpression, ArithmeticExpression)
case multiplication(ArithmeticExpression, ArithmeticExpression)
func evaluate() -> Int {
switch self {
case let .number(value):
return value
case let .addition(left, right):
return left.evaluate() + right.evaluate()
case let .multiplication(left, right):
return left.evaluate() * right.evaluate()
}
}
}
let expr = ArithmeticExpression.addition(.number(5), .multiplication(.number(2), .number(3)))
print(expr.evaluate()) // 5 + (2 * 3) = 11
Methods and Properties
Enums can have methods, computed properties, and static members.
Example:
enum TrafficLight {
case red, yellow, green
var duration: Int {
switch self {
case .red: return 30
case .yellow: return 5
case .green: return 20
}
}
func next() -> TrafficLight {
switch self {
case .red: return .green
case .green: return .yellow
case .yellow: return .red
}
}
static func defaultLight() -> TrafficLight {
return .red
}
}
var light = TrafficLight.red
print(light.duration) // 30
light = light.next() // .green
print(TrafficLight.defaultLight()) // .red
CaseIterable Protocol
Generate a collection of all cases with CaseIterable
.
Example:
enum Day: CaseIterable {
case monday, tuesday, wednesday, thursday, friday, saturday, sunday
}
print(Day.allCases.count) // 7
for day in Day.allCases {
print(day)
}
Frozen Enums
Use @frozen
(in libraries) to guarantee no new cases are added, improving performance.
Example:
@frozen enum LogLevel {
case debug
case info
case error
}
Enums with Protocols
Enums can conform to protocols.
Example:
protocol Describable {
var description: String { get }
}
enum FileType: String, Describable {
case pdf, doc, txt
var description: String {
return "File type: \(rawValue.uppercased())"
}
}
let file = FileType.pdf
print(file.description) // "File type: PDF"
Best Practices
- Use Enums for States: Model finite states (e.g., UI modes, API responses).
- Associated Values: Store variant-specific data.
- Raw Values: Use for serialization or external representation.
- CaseIterable: Iterate over cases when needed.
- Methods: Add logic to enums for self-contained behavior.
- Frozen Enums: Use in libraries for performance and stability.
- Exhaustive Switches: Avoid
default
for enums to catch future changes.
Troubleshooting
- Non-Exhaustive Switch: Add all cases or
default
. - Raw Value Failure: Verify input matches raw value type.
- Recursive Enum Crash: Ensure
indirect
is used correctly. - Protocol Conformance: Check required members are implemented.
- Performance: Avoid complex logic in enum methods for critical paths.
Example: Comprehensive Enum Usage
enum NetworkRequest: CaseIterable, Describable {
case get(url: String)
case post(url: String, data: [String: Any])
case delete(id: Int)
var description: String {
switch self {
case let .get(url):
return "GET request to \(url)"
case let .post(url, data):
return "POST request to \(url) with \(data)"
case let .delete(id):
return "DELETE request for ID \(id)"
}
}
var method: String {
switch self {
case .get: return "GET"
case .post: return "POST"
case .delete: return "DELETE"
}
}
static var allMethods: [String] {
allCases.map { $0.method }
}
}
let requests: [NetworkRequest] = [
.get(url: "https://api.example.com/users"),
.post(url: "https://api.example.com/users", data: ["name": "Alice"]),
.delete(id: 123)
]
for request in requests {
print(request.description)
}
print(NetworkRequest.allMethods) // ["GET", "POST", "DELETE"]