Skip to main content

Swift Pattern Matching

Pattern matching is one of Swift's most powerful features that allows you to check values against specific patterns and extract information when appropriate. It works particularly well with enumerations, providing an elegant way to handle different cases and their associated values.

Introduction to Pattern Matching

Pattern matching in Swift is primarily done using the switch statement, allowing you to compare a value against multiple possible patterns. When working with enumerations, pattern matching becomes especially powerful as it enables you to:

  • Match specific enumeration cases
  • Extract associated values from enumeration cases
  • Apply additional conditions to matches
  • Provide default fallback patterns

Let's explore how pattern matching works with Swift enumerations!

Basic Pattern Matching with Enumerations

The simplest form of pattern matching is checking which case an enumeration value represents.

swift
enum Weather {
case sunny
case cloudy
case rainy
case snowy
}

let todayWeather = Weather.rainy

switch todayWeather {
case .sunny:
print("It's a sunny day!")
case .cloudy:
print("It's cloudy today.")
case .rainy:
print("Don't forget your umbrella!")
case .snowy:
print("Time to build a snowman!")
}

Output:

Don't forget your umbrella!

In this example, the switch statement matches the todayWeather value against each case of the Weather enum. When it finds the matching case (.rainy), it executes the corresponding code.

Extracting Associated Values

One of the most powerful aspects of pattern matching with enumerations is the ability to extract associated values.

swift
enum Product {
case book(title: String, author: String, price: Double)
case electronics(name: String, price: Double)
case clothing(type: String, size: String, price: Double)
}

let myPurchase = Product.book(title: "Swift Programming", author: "John Appleseed", price: 29.99)

switch myPurchase {
case .book(let title, let author, let price):
print("Book: \(title) by \(author), $\(price)")
case .electronics(let name, let price):
print("Electronics: \(name), $\(price)")
case .clothing(let type, let size, let price):
print("Clothing: \(type) size \(size), $\(price)")
}

Output:

Book: Swift Programming by John Appleseed, $29.99

In this example, we extract the associated values (title, author, and price) from the book case of our Product enum.

Shorthand Syntax for Value Extraction

You can use a shorthand syntax when you want to extract all associated values:

swift
switch myPurchase {
case let .book(title, author, price):
print("Book: \(title) by \(author), $\(price)")
case let .electronics(name, price):
print("Electronics: \(name), $\(price)")
case let .clothing(type, size, price):
print("Clothing: \(type) size \(size), $\(price)")
}

This produces the same output as the previous example but with more concise code.

Pattern Matching with Where Clauses

You can add additional conditions to your pattern matches using where clauses:

swift
enum Temperature {
case celsius(Double)
case fahrenheit(Double)
}

let currentTemp = Temperature.celsius(23.5)

switch currentTemp {
case .celsius(let temp) where temp > 25:
print("It's hot at \(temp)°C")
case .celsius(let temp) where temp < 10:
print("It's cold at \(temp)°C")
case .celsius(let temp):
print("It's pleasant at \(temp)°C")
case .fahrenheit(let temp) where temp > 77:
print("It's hot at \(temp)°F")
case .fahrenheit(let temp) where temp < 50:
print("It's cold at \(temp)°F")
case .fahrenheit(let temp):
print("It's pleasant at \(temp)°F")
}

Output:

It's pleasant at 23.5°C

The where clause allows us to add conditional logic to our pattern matching, providing more granular control over when a case matches.

Matching Multiple Patterns

You can match multiple patterns in a single case by separating them with commas:

swift
enum Direction {
case north, south, east, west
}

let heading = Direction.west

switch heading {
case .north, .south:
print("Traveling along the meridian")
case .east, .west:
print("Traveling along the equator")
}

Output:

Traveling along the equator

Binding Part of a Pattern with Value Bindings

You can bind some associated values while ignoring others using an underscore (_):

swift
enum ShippingStatus {
case processing
case shipped(Date)
case delivered(Date)
case returned(Date, reason: String)
}

let packageStatus = ShippingStatus.returned(Date(), reason: "Wrong size")

switch packageStatus {
case .processing:
print("Your order is being processed")
case .shipped(let date):
print("Your order was shipped on \(date)")
case .delivered(let date):
print("Your order was delivered on \(date)")
case .returned(_, let reason):
print("Your order was returned. Reason: \(reason)")
}

Output:

Your order was returned. Reason: Wrong size

In this example, we extract only the reason for the return while ignoring the date.

Pattern Matching in If Statements

You can also use pattern matching directly in if statements, which is useful when you only care about specific cases:

swift
enum LoginResult {
case success(username: String)
case failure(message: String)
}

let result = LoginResult.success(username: "swiftUser123")

if case .success(let username) = result {
print("Welcome back, \(username)!")
}

Output:

Welcome back, swiftUser123!

This is a more concise way to check for a specific case when you don't need a full switch statement.

Real-World Application: State Management

Pattern matching is particularly useful for state management in applications. Let's look at a practical example for a simple network request:

swift
enum NetworkResponse {
case success(data: Data, statusCode: Int)
case failure(error: Error)
case noConnection
}

func handleResponse(_ response: NetworkResponse) {
switch response {
case .success(let data, let statusCode) where statusCode == 200:
print("Request successful with \(data.count) bytes of data")
case .success(_, let statusCode) where statusCode >= 300 && statusCode < 400:
print("Redirection with status code \(statusCode)")
case .success(_, let statusCode):
print("Unexpected status code: \(statusCode)")
case .failure(let error):
print("Request failed with error: \(error.localizedDescription)")
case .noConnection:
print("No internet connection available")
}
}

// Example usage
let sampleResponse = NetworkResponse.success(data: Data(repeating: 0, count: 1024), statusCode: 200)
handleResponse(sampleResponse)

Output:

Request successful with 1024 bytes of data

In this example, we handle different network response scenarios using pattern matching, making the code more expressive and easier to maintain.

Pattern Matching with Optionals

Swift enumerations are the foundation of optionals, and pattern matching works well with them:

swift
let username: String? = "swiftUser123"
let password: String? = nil

switch (username, password) {
case (let username?, let password?):
print("Both username and password provided: \(username), \(password)")
case (let username?, nil):
print("Only username provided: \(username)")
case (nil, let password?):
print("Only password provided: \(password)")
case (nil, nil):
print("No credentials provided")
}

Output:

Only username provided: swiftUser123

The ? pattern matches values that are not nil and unwraps them in the process.

Summary

Pattern matching in Swift is a powerful feature that works exceptionally well with enumerations. It allows you to:

  • Match specific enum cases
  • Extract associated values
  • Add conditional logic with where clauses
  • Match multiple patterns in a single case
  • Ignore parts of patterns with underscores
  • Use pattern matching in if statements and other contexts

By mastering pattern matching, you'll be able to write more expressive, safer, and more concise code when working with Swift enumerations.

Exercises

To practice your pattern matching skills, try these exercises:

  1. Create an enum MediaContent with cases for different types of media (image, video, audio) with appropriate associated values. Write a switch statement that handles each case and extracts the associated values.

  2. Implement a function that takes an optional value and uses pattern matching to either extract the value or provide a default.

  3. Create a state machine for a traffic light using enums and pattern matching to determine what the next state should be.

Additional Resources

Pattern matching is one of the features that truly sets Swift apart as a modern programming language. Keep practicing and exploring its capabilities to become more proficient in Swift programming!



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)