Pattern matching is a fundamental feature of Swift that makes code safer and cleaner. Historically, pattern matching has evolved as a means to work more expressively and safely with enums, especially with associated values. Unlike other languages, Swift allows unpacking nested values directly in if, guard, and switch statements.
Problem: For non-standard enums, particularly with nested associated values, it’s easy to make mistakes in the structure of cases or underestimate the constraints of the main switch. The difference between using if case, guard case, and classic switch is often not obvious.
Solution: Apply different approaches to pattern matching depending on the scenario, and for complex nested structures — explicitly specify names and use where.
Code example:
enum NetworkState { case success(User) case failure(Error, code: Int) case loading(progress: Double) } let state = NetworkState.failure(SomeError(), code: 401) if case let .failure(error, code) = state, code == 401 { print("Unauthorized: \(error)") } guard case .success(let user) = state else { return } print(user) switch state { case .success(let user): print("Welcome, \(user.name)") case .failure(let error, let code) where code == 404: print("Not Found: \(error)") case .failure(_, let code): print("Failure with code: \(code)") case .loading(let progress): print("Progress: \(progress)") }
Key features:
What happens if not all enum cases are specified in a switch?
A compilation error (if the enum is non-optional) or a warning/requirement for a default case if not all cases are covered. For Optionals, it’s sufficient to specify only .some and .none.
Can you perform pattern matching with nested enums and associated values?
Yes. However, the syntax gets more complicated. For nested values, you can unpack multiple levels at once:
enum Outer { case inner(Inner) } enum Inner { case item(id: Int) } let e = Outer.inner(.item(id: 5)) if case let .inner(.item(id)) = e { print(id) }
What is the difference between guard case and if case in pattern matching?
guard case checks the condition and executes an exit block (return, throw, break) on failure. It is commonly used for requirements: if it doesn’t match, exit.
if case is a construct suitable for one-liner conditional handlers without exiting the function.
A developer uses switch for an enum with multiple cases but fails to cover a new case. The code compiles, but the new scenario isn't handled, causing the application to crash at runtime.
Pros:
Cons:
A developer explicitly covers all cases, uses where for local filters, and unpacks nested values through nested let bindings.
Pros:
Cons: