Swift: Swift Enums and Pattern Matching Tutorial
Enums in Swift are far more than "a set of constants" -- they are first-class citizens that can carry data, support methods, and even define themselves recursively. They are the best tool for building state machines and result types.
1. What You'll Learn
- Basic enum syntax and raw value usage
- How associated values let enums carry additional data
- Various pattern matching styles (switch, if-case, guard-case)
- Recursive enums with the indirect keyword
- Practical application of enums in state machines
2. A Mobile Developer's Real Story
(1) Pain Point: Hard to Express API Response States
Alice ran into a tricky problem while building the networking layer for a news app. An API request could return multiple results:
// Inelegant approach: multiple optional variables to represent state
var data: NewsResponse? = nil
var error: Error? = nil
var isLoading = false
This approach has three problems: ambiguous meaning when data and error are both present, forgetting to handle a state causes crashes, and scattered logic is hard to maintain.
(2) The Enum with Associated Values Solution
Use a single enum to unify all states, with each case carrying its own data:
enum NetworkResult<T> {
case success(T)
case failure(Error)
case loading
}
NetworkResult<[NewsArticle]> either carries article data (success), error information (failure), or the loading state -- three mutually exclusive states with clear semantics.
(3) Benefits: State Management from Chaos to Clarity
| Dimension | Multiple Optional Variables | Enum with Associated Values |
|---|---|---|
| State count | 2^N illegal combinations | N precise states |
| Semantic clarity | data/error may coexist | Each state carries its own data |
| Handling completeness | Easy to miss cases | switch exhaustiveness check |
| Code maintainability | Scattered in if-else | Centralized in one type |
3. Enum Basics
Enums define a set of related values. Swift enums are more flexible than those in other languages -- they can associate methods, computed properties, and subscripts.
(1) Basic Enums and Raw Values
graph LR
A["enum Compass {\n case north\n case south\n case east\n case west\n}"] --> B["Compass.north"]
A --> C["Compass.south"]
A --> D["Compass.east"]
A --> E["Compass.west"]
A raw value is a fixed value behind each enum case, which can be of types like String or Int.
| Raw Value Type | Declaration | Value |
|---|---|---|
| Int | enum Status: Int { case ok = 200 } |
Integer |
| String | enum Direction: String { case north = "N" } |
String |
| Implicit Int | enum Grade: Int { case a, b, c } |
Increments from 0 |
| Implicit String | enum Code: String { case red, green } |
Same as case name |
▶ Example: HTTP Status Code Enum
// ============================================
// HTTP status code enum using raw values
// ============================================
enum HTTPStatus: Int {
case ok = 200
case created = 201
case badRequest = 400
case unauthorized = 401
case notFound = 404
case serverError = 500
}
let status = HTTPStatus.notFound
print("Code: \(status.rawValue)")
print("Name: \(status)")
// Create from raw value
let parsed = HTTPStatus(rawValue: 200)
print("Parsed: \(parsed ?? .serverError)")
Output:
TEXT 📖 Display onlyCode: 404 Name: notFound Parsed: ok
(2) Enum Methods and Computed Properties
Enums can define methods and properties just like structs.
▶ Example: Enum with Methods
// ============================================
// Custom enum method: determine status category
// ============================================
enum HTTPStatus: Int {
case ok = 200
case created = 201
case badRequest = 400
case unauthorized = 401
case notFound = 404
case serverError = 500
var isSuccess: Bool {
rawValue >= 200 && rawValue < 300
}
var description: String {
switch self {
case .ok: return "OK"
case .created: return "Created"
case .badRequest: return "Bad Request"
case .unauthorized: return "Unauthorized"
case .notFound: return "Not Found"
case .serverError: return "Internal Server Error"
}
}
}
let code = HTTPStatus.ok
print("\(code.rawValue) \(code.description) - Success: \(code.isSuccess)")
Output:
TEXT 📖 Display only200 OK - Success: true
4. Associated Values and Pattern Matching
Associated values are a core feature that sets Swift enums apart from other languages -- each case can carry additional data of different types.
(1) Associated Value Syntax
graph TB
A["NetworkResult<T>"] --> B["success(T) - carries data"]
A --> C["failure(Error) - carries error"]
A --> D["loading - no data"]
B --> E["switch result {\n case .success(let data):\n show(data)\n case .failure(let error):\n show(error)\n case .loading:\n showSpinner()\n}"]
| Feature | Raw Values | Associated Values |
|---|---|---|
| Value type | Fixed constant | Variable data |
| Storage timing | Determined at definition | Passed at creation |
| Per case | All cases have same value type | Each case can differ |
| Use case | Mapping fixed codes | Carrying dynamic data |
▶ Example: Handling Network Request Results
// ============================================
// Associated value enum for handling API responses
// ============================================
enum NetworkResult<T> {
case success(T)
case failure(String)
case loading
}
func handleResponse<T>(_ result: NetworkResult<T>) {
switch result {
case .success(let data):
print("Successfully received data: \(data)")
case .failure(let error):
print("Request failed: \(error)")
case .loading:
print("Loading...")
}
}
handleResponse(NetworkResult.success("User Info"))
handleResponse(NetworkResult.failure("Network connection timed out"))
handleResponse(NetworkResult.loading)
Output:
TEXT 📖 Display onlySuccessfully received data: User Info Request failed: Network connection timed out Loading...
(2) if-case and guard-case Pattern Matching
Besides switch, you can use if case and guard case for single-case matching.
▶ Example: Concise Single-Case Matching
// ============================================
// Concise usage of if-case and guard-case
// ============================================
enum Measurement {
case weight(Double)
case height(Double)
case count(Int)
}
let record = Measurement.weight(75.5)
// if-case: only care about one case
if case .weight(let kg) = record, kg > 70 {
print("Weight exceeds 70kg: \(kg)")
}
// guard-case: early exit
func process(_ m: Measurement) {
guard case .count(let n) = m else {
print("Not a count type")
return
}
print("Count is \(n)")
}
process(.weight(65))
process(.count(42))
Output:
TEXT 📖 Display onlyWeight exceeds 70kg: 75.5 Not a count type Count is 42
5. Recursive Enums
A recursive enum is one whose associated values reference the enum itself, suitable for expressing tree-like or nested structures.
(1) The indirect Keyword
Use indirect to mark a recursive case or the entire enum, telling the compiler that indirect storage is needed.
| Syntax | Description |
|---|---|
indirect case expression(Expression) |
Single recursive case |
indirect enum Expression { ... } |
Entire enum allows recursion |
▶ Example: Evaluating a Math Expression
// ============================================
// Recursive enum: math expression tree
// ============================================
indirect enum ArithmeticExpression {
case number(Int)
case addition(ArithmeticExpression, ArithmeticExpression)
case multiplication(ArithmeticExpression, ArithmeticExpression)
}
func evaluate(_ expr: ArithmeticExpression) -> Int {
switch expr {
case .number(let value):
return value
case .addition(let left, let right):
return evaluate(left) + evaluate(right)
case .multiplication(let left, let right):
return evaluate(left) * evaluate(right)
}
}
// Build the expression (3 + 5) * 2
let three = ArithmeticExpression.number(3)
let five = ArithmeticExpression.number(5)
let sum = ArithmeticExpression.addition(three, five)
let product = ArithmeticExpression.multiplication(sum, .number(2))
print("(3 + 5) * 2 = \(evaluate(product))")
Output:
TEXT 📖 Display only(3 + 5) * 2 = 16
6. Full Example: Order State Machine
// ============================================
// Full example: E-commerce order state machine
// Features: order state transitions + data processing
// ============================================
import Foundation
// 1. Order status enum (with associated values)
enum OrderStatus {
case pending // Pending payment
case paid(Date) // Paid (with payment time)
case shipped(trackingNumber: String) // Shipped (with tracking number)
case delivered(Date) // Delivered (with receipt time)
case cancelled(reason: String) // Cancelled (with reason)
var description: String {
switch self {
case .pending: return "Pending"
case .paid(let date): return "Paid (\(date.formatted()))"
case .shipped(let tracking): return "Shipped (Tracking: \(tracking))"
case .delivered(let date): return "Delivered (\(date.formatted()))"
case .cancelled(let reason): return "Cancelled: \(reason)"
}
}
var isFinalState: Bool {
if case .delivered = self { return true }
if case .cancelled = self { return true }
return false
}
}
// 2. Usage example
let now = Date()
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
let statuses: [OrderStatus] = [
.pending,
.paid(now),
.shipped(trackingNumber: "SF1234567890"),
.delivered(now),
.cancelled(reason: "Cancelled by user")
]
for status in statuses {
let final = status.isFinalState ? " (Final)" : ""
print("\(status.description)\(final)")
}
Output:
TEXT 📖 Display onlyPending Paid (2026-07-30) Shipped (Tracking: SF1234567890) Delivered (2026-07-30) (Final) Cancelled: Cancelled by user (Final)
❓ FAQ
enum Optional<T> is a classic example of associated values + generics: case none or case some(T). Associated values can reference generic parameters.ArithmeticExpression.addition has associated values of type ArithmeticExpression, forming a recursive reference that requires indirect.📖 Summary
- Swift enums are first-class types, supporting methods, computed properties, and protocol conformance
- Raw values bind a fixed constant value to each case
- Associated values let each case carry additional data of different types
- Pattern matching (switch, if-case, guard-case) safely extracts enum data
- Recursive enums use the indirect keyword to express tree-like nested structures
- Enums are the ideal tool for building state machines, result types, and optional types
📝 Exercises
- Basic: Define a
TrafficLightenum with three cases: red, yellow, green. Add adurationcomputed property that returns the duration in seconds for each light. - Intermediate: Use associated values to define an
Either<L, R>enum (similar to Rust's Result) withleft(L)andright(R)cases, and add amapmethod to transform the right value. - Challenge: Use a recursive enum to implement a JSON type (supporting null, bool, int, string, array, dictionary), and write a function to format a JSON enum value as an indented string.