Enumerations and associated values

Swift · Essentials · 12 min

Enums with payloads model states and results precisely and let the compiler check every case is handled.

An enum defines a closed set of cases: enum Direction { case north, south, east, west }. When the type is known you can drop it: let d: Direction = .north.

Enums can have raw values of a single type: enum Planet: Int { case mercury = 1, venus } (venus gets 2). Planet(rawValue: 3) returns an optional, since the number may not match a case. String raw values default to the case name. Add CaseIterable to get allCases.

More powerful are associated values: each case carries its own payload, of any type.

enum Payment {
    case cash
    case card(last4: String)
    case transfer(iban: String, amount: Double)
}

You extract payloads in a switch with case .card(let last4) or case let .transfer(iban, amount). Because switch is exhaustive, adding a new case makes the compiler point at every place that must handle it — a big reason to model state with enums rather than flags.

Enums can have computed properties, methods (mutating to change self) and conform to protocols. if case .card(let n) = payment { } tests a single case. indirect allows recursive enums such as expression trees.

Optional and Result are themselves enums with associated values.

Example

enum LoadState {
    case idle
    case loading(progress: Double)
    case loaded([String])
    case failed(message: String)

    var isBusy: Bool {
        if case .loading = self { return true }
        return false
    }
}

func render(_ s: LoadState) -> String {
    switch s {
    case .idle: return "Tap to load"
    case .loading(let p): return "Loading \(Int(p * 100))%"
    case .loaded(let rows) where rows.isEmpty: return "Nothing here"
    case .loaded(let rows): return "\(rows.count) rows"
    case .failed(let message): return "Error: \(message)"
    }
}

print(render(.loading(progress: 0.4)))   // Loading 40%
print(render(.loaded(["a", "b"])))       // 2 rows
print(LoadState.idle.isBusy)             // false

Remember

  • Associated values attach different data to each case
  • switch over an enum must be exhaustive — the compiler finds missing cases
  • Raw-value init Enum(rawValue:) is failable

Watch out for

  • Adding default: to every enum switch, which hides new cases from the compiler.

Check yourself

  1. What does `Planet(rawValue: 42)` return when no case has raw value 42?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.swift.org/swift-book/documentation/the-swift-programming-language/enumerations/