ProgrammingiOS Developer

How does pattern matching work with structures and enums with associated values in Swift? What is the difference between matching using `if case`, `guard case`, and `switch`, and what is the role of case names and nesting in complex enums?

Pass interviews with Hintsage AI assistant

Answer.

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:

  • Pattern matching is possible in switch, if case, guard case, and also through let/var binding.
  • Filtering can be done using where.
  • In a switch, pattern matching allows you to explicitly describe all cases of an enum, ensuring there are no missing scenarios.

Trick questions.

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.

Common mistakes and anti-patterns

  • Omitting coverage for all enum cases in a switch leads to bugs and crashes.
  • Errors when unpacking nested values: mixed-up names or nesting levels.
  • Using if/switch for optional values without considering the .none-case, leading to missing scenarios.

Real-life example

Negative case

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:

  • Easy to add a minimal handler via default

Cons:

  • Missed scenarios, hard to trace the cause
  • Potential crash upon introduction of new cases

Positive case

A developer explicitly covers all cases, uses where for local filters, and unpacks nested values through nested let bindings.

Pros:

  • Full type safety
  • All scenarios are explicitly described

Cons:

  • Sometimes verbose, redundant switch or double pattern matching