반응형

Swift에서 switch문의 pattern matching

 

Swift의 switch는 단순한 값 비교를 넘어 패턴 매칭(pattern matching)을 지원합니다. case에 들어가는 것이 “상수/리터럴”만이 아니라 패턴이며, 내부적으로는 ~= 연산자를 활용해 매칭합니다. 이 문서는 switch의 문법, 다양한 패턴, if/guard/for에서의 패턴 매칭 활용, 실무 팁까지 한 번에 정리합니다.


 

1. 핵심 요약

  • Swift의 switch는 모든 경우를 처리해야 하는 포괄성(exhaustiveness)을 요구합니다(열거형에 특히 유리).
  • case에는 값 바인딩, 튜플, 열거형 연관값, 범위/부분 범위, 와일드카드 등 다양한 패턴을 쓸 수 있습니다.
  • where절로 조건 추가가 가능하고, if case / guard case / for case 구문으로 스위치 없이도 패턴 매칭을 사용할 수 있습니다.
  • @unknown default를 사용하면 미래에 추가될 enum 케이스에 대비할 수 있습니다.

 

2. 기본 문법

let n = 42

switch n {
case 0:
    print("zero")
case 1...9:
    print("one digit")
case 10..<100:
    print("two digits")
default:
    print("100 or above")
}
  • default는 누락된 모든 경우를 포괄합니다. (enum에서 모든 케이스를 명시하면 default가 불필요)
  • Swift의 switch는 암묵적 fallthrough가 없습니다. 한 case가 끝나면 다음 case로 넘어가지 않습니다. 특별히 원하면 fallthrough 키워드를 명시합니다.

 

3. 패턴 종류 정리


3.1 값 바인딩(Value Binding)

let point = (3, -2)

switch point {
case let (x, y) where x == y:
    print("x == y, x=\(x)")
case let (x, y) where x == -y:
    print("x == -y, (\(x), \(y))")
case let (x, y):
    print("any point (\(x), \(y))")
}
  • let/var로 매칭된 값을 바인딩해 사용할 수 있습니다.
  • where절과 결합해 조건을 추가합니다.

3.2 튜플 패턴(Tuple Pattern)

let p = (0, 5)
switch p {
case (0, 0):              print("origin")
case (0, _):              print("on Y axis")
case (_, 0):              print("on X axis")
case (1...3, 1...10):     print("rect region")
default:                  print("elsewhere")
}

3.3 열거형 + 연관값(Enum Associated Values)

enum Event {
    case login(user: String)
    case logout
    case message(text: String, unread: Int)
}

let e = Event.message(text: "Hello", unread: 2)

switch e {
case .login(let user):
    print("login:", user)
case .logout:
    print("logout")
case .message(let text, let unread) where unread > 0:
    print("\(text), unread=\(unread)")
case .message(let text, _):
    print("\(text)")
}
  • 연관값을 분해하면서 where로 조건을 주는 패턴이 강력합니다.

3.4 옵셔널 패턴(Optional Pattern)

let maybe: Int? = 10

switch maybe {
case .some(let value):
    print("value:", value)
case .none:
    print("nil")
}
  • 같은 로직을 if case let .some(value) = maybe로도 쓸 수 있습니다(아래 §6 참조).

3.5 범위/부분 범위(Range / Partial Range)

let age = 17

switch age {
case ..<0:          print("invalid")
case 0...12:        print("child")
case 13..<20:       print("teen")
case 20...:         print("adult")
default:            break
}
  • ..<(미포함), ...(포함), 부분 범위(..<x, x...) 등 모든 범위 리터럴이 패턴으로 동작합니다.

3.6 타입캐스팅 패턴(Type Casting: is / as)

class Animal {}
class Dog: Animal { let name: String; init(_ n: String){ name = n } }
class Cat: Animal {}

func greet(_ a: Animal) {
    switch a {
    case let d as Dog:
        print("Dog:", d.name)
    case is Cat:
        print("Cat")
    default:
        print("Animal")
    }
}
  • 다운캐스팅 결과를 바인딩하거나(as), 타입 여부만 검사할 수 있습니다(is).

3.7 와일드카드/패턴 조합

let t = (status: 200, payload: "ok")
switch t {
case (200, _):          print("success")      // 값 무시
case (400..<500, _):    print("client error")
case (_, let body):
    print("other -> \(body)")
}

 

4. where 절 (조건부 패턴)

case에 부가 조건을 달아 정밀 제어가 가능합니다.

let score = (name: "A", point: 87)

switch score {
case let (name, p) where p >= 90:
    print("\(name): A")
case let (name, p) where p >= 80:
    print("\(name): B")
default:
    print("C or below")
}

 

5. 열거형과 포괄성(Exhaustiveness)

enum NetworkState { case idle, loading, success, failure }

func render(_ s: NetworkState) {
    switch s {
    case .idle:    print("idle")
    case .loading: print("loading")
    case .success: print("success")
    case .failure: print("failure")
    }
}
  • 모든 케이스를 나열하여 컴파일 타임 안전성을 확보합니다.
  • 외부 라이브러리의 enum 등 미래에 케이스가 추가될 가능성이 있으면 @unknown default를 사용합니다.
switch state {
case .idle, .loading, .success:
    ...
@unknown default:
    // 미래에 추가될 케이스 방어
    logger.warning("unknown state: \(state)")
}

 

6. if case / guard case / for case

switch 없이도 패턴 매칭만 사용할 수 있습니다.

6.1 if case

let x: Int? = 5
if case let .some(v) = x, v > 3 {
    print("v>3:", v)
}

6.2 guard case (조기 종료)

func handle(_ e: Event) {
    guard case let .login(user) = e else { return }
    print("Hello,", user)   // 여기선 login인 경우만 실행
}

6.3 for case

let arr: [Int?] = [1, nil, 2, nil, 3]
for case let .some(v) in arr {
    print(v)   // 1, 2, 3
}

 

7. fallthrough / 바인딩 재사용 / 우선순위 팁

  • Swift는 암묵적 fallthrough 없음: 같은 로직을 공유하려면 다중 case를 쉼표로 나열하거나, 공통 함수를 호출하세요.
switch c {
  case "a", "e", "i", "o", "u":
    print("vowel")
  default:
    print("consonant")
}
  • 바인딩 이름은 case 스코프 내에서만 유효합니다. 동일 이름을 다음 case에서 다시 바인딩해도 됩니다.
  • case let v? 옵셔널 단축 패턴:
let a: Int? = 10
switch a {
  case let v?:
    print(v) // .some(v) 단축
  case nil:
    print("nil")
}

 


 

8. 커스텀 패턴과 ~= 연산자

switch는 내부적으로 casePattern ~= value 호출로 매칭합니다. ~=를 오버로드하면 커스텀 패턴을 만들 수 있습니다.

// 날짜가 특정 범위에 속하는지 매칭하는 예시
import Foundation

func ~= (pattern: ClosedRange<Date>, value: Date) -> Bool {
    pattern.contains(value)
}

let now = Date()
let today = now...now.addingTimeInterval(60*60*24)

switch now {
case today:
    print("today!")
default:
    break
}

정규식(Regex)과 결합하여 문자열 패턴 매칭을 구현할 수도 있습니다(직접 ~= 오버로드 또는 Swift Regex API 활용).


 

9. 오류 처리와 패턴 매칭 (보너스: do-catch)

catch 절도 패턴 매칭입니다. 특정 오류 타입/연관값을 분해할 수 있습니다.

enum NetErr: Error {
    case status(code: Int)
    case underlying(Error)
}

do {
    throw NetErr.status(code: 404)
} catch NetErr.status(let code) where (400..<500).contains(code) {
    print("client error:", code)
} catch {
    print("other:", error)
}

 

10. 실무 예제 모음


10.1 라우팅 (경로·파라미터 분해)

enum Route {
    case home
    case profile(id: Int)
    case search(query: String, page: Int)
}

func handle(_ r: Route) {
    switch r {
    case .home:
        showHome()
    case .profile(let id):
        showProfile(id: id)
    case .search(let q, let p) where p > 0:
        search(q, page: p)
    default:
        assertionFailure("invalid route param")
    }
}

10.2 네트워크 응답 상태 분기

enum State { case idle, loading, success(Data), failure(Error) }

func render(_ s: State) {
    switch s {
    case .idle:
        showIdle()
    case .loading:
        showLoading()
    case .success(let data):
        renderData(data)
    case .failure(let error):
        renderError(error)
    }
}

10.3 좌표/영역 판단

typealias Point = (x: Int, y: Int)
func locate(_ p: Point) -> String {
    switch p {
    case (0, 0): return "origin"
    case (let x, 0): return "x-axis x=\(x)"
    case (0, let y): return "y-axis y=\(y)"
    case (-10...10, -10...10): return "near center"
    default: return "far"
    }
}

10.4 문자열 카테고리

func category(of ch: Character) -> String {
    switch ch {
    case "0"..."9": return "digit"
    case "a"..."z", "A"..."Z": return "letter"
    case " ", "\t", "\n": return "whitespace"
    default: return "other"
    }
}

 

11. 참고 사항

  • enum + switch 상태 기계를 모델링하면 컴파일러가 포괄성을 보장해 줍니다.
  • 여러 조건을 한 case로 결합하고, 세부 분기는 where 또는 내부 switch로 정리하세요.
  • 공개 API의 enum 분기에는 @unknown default 로 미래 호환성을 확보하세요.
  • if/guard/for case를 사용해 스위치 없이도 간결한 패턴 매칭을 적극 활용하세요.
  • 반복 분기에서 fallthrough 대신 쉼표로 다중 case를 쓰세요.

 

12. 참조 링크


 

13. 결론

Swift의 switch는 강력한 패턴 매칭 엔진입니다. 튜플·열거형·옵셔널·범위·타입 패턴과 where를 조합하면 간결하면서 타입 안전한 분기를 구현할 수 있습니다. 실무에서는 enum 상태 모델링 + 포괄적 switch + @unknown default 조합이 특히 강력합니다.

반응형
Posted by 까칠코더
,