2025/07/22

18. Swift 고급 패턴 매칭: 복잡한 데이터 구조 효과적으로 다루기

안녕하세요! 이번 블로그에서는 Swift의 문법적 유연성을 더해주는 강력한 도구인 패턴 매칭(Pattern Matching)에 대해서 정리해 보겠습니다. 특히, 복잡한 데이터 구조를 효과적으로 분석하고 처리하는 고급 패턴 매칭 기법들을 중심으로 살펴보겠습니다. 🧩

여러분은 이미 Swift에서 패턴 매칭을 많이 사용해왔을 겁니다. 예를 들어 if let으로 옵셔널 바인딩을 하거나, switch 문에서 특정 값을 비교하는 것이 모두 패턴 매칭의 일종입니다. 하지만 Swift의 패턴 매칭은 단순한 값 비교를 넘어, 복잡한 데이터 구조의 형태를 분석하고, 그 안의 값을 추출하며, 특정 조건을 만족하는 경우에만 코드를 실행할 수 있는 매우 정교한 기능을 제공합니다. 이를 통해 코드를 더욱 간결하고 표현력이 풍부하게 만들 수 있습니다.


1. 패턴(Patterns)이란 무엇인가요?

패턴은 단일 값, 복합 값 또는 값의 특정 타입을 위한 구조를 나타냅니다. 예를 들어, 특정 범위의 숫자, 특정 열거형 케이스, 또는 특정 타입의 인스턴스와 같은 것들이죠. Swift에서 패턴은 다음과 같은 곳에서 사용됩니다:

  • 할당(Assignment): let x = 10 (값 바인딩 패턴)
  • 조건문(Conditional Statements): if let, guard let (옵셔널 패턴)
  • 반복문(Loop Statements): for case let (패턴 매칭)
  • switch 문 케이스(Case Statements): 다양한 종류의 패턴
  • catch 절(Catch Clauses): 에러 타입 매칭

간단히 말해, 패턴은 값을 분해(decompose)하거나 일치(match)시키기 위한 규칙이라고 생각할 수 있습니다.

2. switch 문에서의 고급 패턴 매칭

switch 문은 Swift에서 가장 강력한 패턴 매칭 도구 중 하나입니다. 다양한 종류의 패턴을 사용하여 값을 비교하고, 복잡한 조건을 처리할 수 있습니다.

2.1. 값 바인딩 패턴 (Value-Binding Pattern)

상수나 변수 이름을 사용하여 일치하는 값을 캡처하고 해당 이름에 바인딩합니다.


let somePoint = (1, 5)

switch somePoint {
case (let x, let y): // x와 y에 somePoint의 값 바인딩
    print("점은 (\(x), \(y))에 있습니다.")
}
// 출력: 점은 (1, 5)에 있습니다.

2.2. 와일드카드 패턴 (Wildcard Pattern)

_를 사용하여 특정 부분을 무시하고 싶을 때 사용합니다.


let anotherPoint = (2, 0)

switch anotherPoint {
case (let x, 0): // y 값이 0인 경우 x 값만 캡처
    print("x축에 있는 점으로, x 값은 \(x)입니다.")
case (0, let y): // x 값이 0인 경우 y 값만 캡처
    print("y축에 있는 점으로, y 값은 \(y)입니다.")
default:
    print("어느 축에도 없는 점입니다.")
}
// 출력: x축에 있는 점으로, x 값은 2입니다.

2.3. 옵셔널 패턴 (Optional Pattern)

옵셔널 값을 언래핑하고 싶을 때 사용합니다. if let이나 guard let에서 주로 보지만, switch에서도 사용할 수 있습니다.


let optionalValue: Int? = 5
let anotherOptionalValue: Int? = nil

switch optionalValue {
case .some(let value): // 값이 있는 경우 value에 바인딩
    print("값이 존재합니다: \(value)")
case .none: // 값이 없는 경우
    print("값이 nil입니다.")
}
// 출력: 값이 존재합니다: 5

switch anotherOptionalValue {
case let value?: // .some(let value)의 축약형
    print("값이 존재합니다: \(value)")
case nil:
    print("값이 nil입니다.")
}
// 출력: 값이 nil입니다.

2.4. 타입 캐스팅 패턴 (Type-Casting Pattern)

is 또는 as 키워드를 사용하여 특정 타입에 일치하는지 확인하거나 다운캐스팅합니다.


class Vehicle {
    var numberOfWheels: Int
    init(numberOfWheels: Int) { self.numberOfWheels = numberOfWheels }
}
class Car: Vehicle {
    var brand: String
    init(brand: String) { self.brand = brand; super.init(numberOfWheels: 4) }
}
class Bicycle: Vehicle {
    var hasBasket: Bool
    init(hasBasket: Bool) { self.hasBasket = hasBasket; super.init(numberOfWheels: 2) }
}

let vehicles: [Vehicle] = [Car(brand: "BMW"), Bicycle(hasBasket: true), Vehicle(numberOfWheels: 6)]

for vehicle in vehicles {
    switch vehicle {
    case let car as Car: // Vehicle이 Car 타입이면 car 상수에 바인딩
        print("이것은 \(car.brand) 브랜드의 자동차입니다.")
    case let bicycle as Bicycle: // Vehicle이 Bicycle 타입이면 bicycle 상수에 바인딩
        print("이것은 바구니가 \(bicycle.hasBasket ? "있는" : "없는") 자전거입니다.")
    case let otherVehicle: // 다른 모든 Vehicle 인스턴스
        print("이것은 바퀴가 \(otherVehicle.numberOfWheels)개인 알 수 없는 종류의 차량입니다.")
    }
}
// 출력:
// 이것은 BMW 브랜드의 자동차입니다.
// 이것은 바구니가 있는 자전거입니다.
// 이것은 바퀴가 6개인 알 수 없는 종류의 차량입니다.

2.5. 표현식 패턴 (where 절과 결합)

case 패턴에 추가 조건을 붙이고 싶을 때 where 절을 사용합니다.


let yetAnotherPoint = (1, -1)

switch yetAnotherPoint {
case let (x, y) where x == y:
    print("점 (\(x), \(y))은 x == y 선 위에 있습니다.")
case let (x, y) where x == -y:
    print("점 (\(x), \(y))은 x == -y 선 위에 있습니다.")
case let (x, y):
    print("점 (\(x), \(y))은 임의의 위치에 있습니다.")
}
// 출력: 점 (1, -1)은 x == -y 선 위에 있습니다.

// 여러 조건 결합
let temperature = 25

switch temperature {
case 0...10 where temperature.isMultiple(of: 2):
    print("추운 짝수 온도입니다.")
case 11...20 where temperature.isMultiple(of: 2):
    print("시원한 짝수 온도입니다.")
case 21...30 where temperature.isMultiple(of: 5):
    print("따뜻한 5의 배수 온도입니다.")
default:
    print("기타 온도입니다.")
}
// 출력: 따뜻한 5의 배수 온도입니다.

3. for-in 루프에서의 패턴 매칭: for case let

for-in 루프에서도 패턴 매칭을 사용하여 컬렉션의 특정 요소만 반복하거나 추출할 수 있습니다. 특히 열거형의 연관 값(associated values)을 필터링할 때 유용합니다.


enum Pet {
    case dog(name: String)
    case cat(name: String, hasOwner: Bool)
    case fish
}

let pets: [Pet] = [.dog(name: "Buddy"), .cat(name: "Whiskers", hasOwner: true), .fish, .dog(name: "Max"), .cat(name: "Shadow", hasOwner: false)]

// 강아지 이름만 출력
for case let .dog(name) in pets {
    print("강아지 이름: \(name)")
}
// 출력:
// 강아지 이름: Buddy
// 강아지 이름: Max

// 주인이 있는 고양이만 출력
for case let .cat(name, true) in pets {
    print("주인이 있는 고양이: \(name)")
}
// 출력: 주인이 있는 고양이: Whiskers

// `where` 절과 함께 사용
for case let .cat(name, hasOwner) in pets where hasOwner == false {
    print("길고양이 이름: \(name)")
}
// 출력: 길고양이 이름: Shadow

for case let 문은 특정 패턴에 일치하는 컬렉션 요소만 효율적으로 순회하고, 동시에 필요한 값을 바인딩할 수 있게 해줍니다.

4. if case let 문: 간결한 조건부 바인딩

if case let 문은 switch 문을 사용하기에는 너무 단순한 단일 케이스 패턴 매칭에 유용합니다. if let과 유사하게 옵셔널 값을 언래핑하거나, 열거형의 특정 케이스를 확인하고 연관 값을 바인딩하는 데 사용됩니다.


enum NetworkStatus {
    case connected(speed: Int)
    case disconnected(reason: String)
    case connecting
}

let currentStatus: NetworkStatus = .connected(speed: 100)

// connected 케이스이고 속도가 50 이상인 경우
if case let .connected(speed) = currentStatus, speed >= 50 {
    print("고속 네트워크에 연결되었습니다! 속도: \(speed) Mbps")
} else {
    print("네트워크 연결 상태를 확인해주세요.")
}
// 출력: 고속 네트워크에 연결되었습니다! 속도: 100 Mbps

let offlineStatus: NetworkStatus = .disconnected(reason: "서버 오류")

if case let .disconnected(reason) = offlineStatus {
    print("네트워크 연결 해제: \(reason)")
}
// 출력: 네트워크 연결 해제: 서버 오류

if case let은 switch의 복잡한 구조 없이 특정 패턴에 대한 빠른 검사와 값 바인딩을 가능하게 합니다.

정리하며

오늘은 Swift의 강력한 고급 패턴 매칭 기능에 대해 자세히 알아보았습니다.

  • 패턴: 값의 구조를 나타내며, 값을 분해하거나 일치시키는 규칙입니다.
  • switch 문: 값 바인딩, 와일드카드, 옵셔널, 타입 캐스팅 패턴과 where 절을 결합하여 복잡한 조건 처리 및 값 추출을 수행합니다.
  • for case let: for-in 루프에서 특정 패턴에 일치하는 요소만 반복하고 값을 바인딩합니다.
  • if case let: 단일 패턴에 대한 간결한 조건부 검사와 값 바인딩을 제공합니다.

고급 패턴 매칭은 여러분의 Swift 코드를 더욱 간결하고, 가독성 높으며, 표현력 있게 만들어 줍니다. 특히 복잡한 열거형, 튜플, 그리고 클래스 계층 구조를 다룰 때 그 진가를 발휘합니다. 오늘 배운 다양한 패턴 매칭 기법들을 여러분의 코드에 적용하여 Swift의 유연성을 최대한 활용해 보세요!

궁금한 점이 있다면 언제든지 댓글로 남겨주세요. 감사합니다.

0 comments:

댓글 쓰기