여러분은 이미 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:
댓글 쓰기