Swift

[Swift] switch-case 문에서의 where절과 unknown 키워드

하이D:) 2023. 9. 27. 13:35

swift에서 switch문은 if문처럼 조건문이며 조건이 만족했을 때 해당 case에서의 코드가 실행되는 것은 대부분 알고 있을 것이다. 하지만 switch문에서 우리가 흔히 사용하는 것처럼 값이 딱 일치하는 case로 분기하는 게 아니라 어떤 조건을 만족했는지에 대한 것을 case문으로 작성하고 만족했을 때 실행문으로 이동하도록 하는 방법은 무엇일까?

 

그리고 switch문의 마지막에는 보통 case 분기처리한 케이스 외에 다른 경우의 수를 위해 default 블록를 추가하는데 default 블록만 추가한다고 분기처리가 안전하다고 볼 수 있을까? unknown 키워드는 언제 쓰일까?

 

 

# 🥨 switch문에서의 바인딩과 where절

우리는 언제 switch문에서 바인딩 where절을 사용할 수 있을까?

num이라는 변수가 7이라는 정수라고 일치하는 값이 아니라 7보다 작은 정수 이면 코드를 실행한다고 가정해 보자, 이때 실수 할 수 있는 경우를 알아보고 정확한 방법을 설명하겠다.

 

틀린 방법

Expression pattern of type 'Bool' cannot match values of type 'Int'

아래 코드처럼 구현하면 위의 오류가 뜰 것이고 'Bool'값을 반환하는 num % 2 == 0 를 case에 적는 것은 옳지 않다.  case에서는 "참"인 값이 나오는 조건을 확인하는 게 아니라 값이 매칭되는지 확인하는 것이기 때문에 아래의 방법은 틀렸다.

let num = 5

switch num{
case num % 2 == 0 :
    print("짝수")
    
default :
    print("default")
    
}

 

옳은 방법

이때 활용할 있는 게 switch 문의 바인딩 where절이다. let n where n < 7 에서 볼 수 있는 것처럼 일단 n으로 값을 바인딩시켜놓고 그 상수를 활용해서 where절의 조건을 만족하는지를 판별하는 것이기 때문에 옳은 방법이다.

let num = 5

switch num{
case let n where n < 7:
    print("7보다 작은 정수")
    
default :
    print("default")
}

 

 

# 🥨 튜플의 switch문 바인딩과 where절

이제 case에서 일치 값이 아닌 조건을 확인하는 방법을 알았기 때문에 튜플 형태를 조건 확인할 때도 이 방법을 활용할 수 있다.

우선 차근차근 알아보자면 switch문에서 튜플을 어떻게 바인딩시키는 걸까?

 

튜플의 switch문 바인딩

아래와 같이 튜플 형태도 바인딩시킬 수 있으며 case (let distance, 0), (0, let distance) 처럼 첫 번째 혹은 두 번째 요소에 0이 있을 경우로 한정시켜서 요소를 바인딩시켜볼 수도 있고 case let (x,y) 처럼 튜플 자체를 바인딩시켜볼 수도 있다.

var coordinate = (0, 5)   // 좌표계

switch coordinate {
case (let distance, 0), (0, let distance):   // x축이나 y축에 있으면 실행
    print("X 또는 Y축 위에 위치하며, \(distance)만큼의 거리가 떨어져 있음")
    
case let (x,y):   // x축이나 y축에 있지 않는 좌표일경우 실행
    print("X축에서 \(x)만큼 떨어져있으며, Y축에서 \(y)만큼 떨어져있다")
}

 

튜플의 switch문 바인딩과 where절

튜플 형태도 switch 문에서 where절을 활용해서 내부 요소에 대해 조건을 판별해 볼 수 있는데 아래 예시와 같이 바인딩 후 where절을 사용하면 되는 것이다.

coordinate = (5, 0)

switch coordinate {
case (let x, let y) where x == y:      //일단 x, y를 상수로 지정해주고, 그리고 나서 비교한다.
    print("(\(x), \(y))의 좌표는 y = x 1차함수의 그래프 위에 있다.")
    
case let (x, y) where x == -y:
    print("(\(x), \(y))의 좌표는 y = -x 1차함수의 그래프 위에 있다.")
    
case let (x, y):
    print("(\(x), \(y))의 좌표는 y = x, 또는 y = -x 그래프가 아닌 임의의 지점에 있다.")
}

 

나는 실제 프로젝트를 할 때 이 방법을 사용한 적은 없지만 switch 문의 특징 중 하나가 if 문보다 가독성이 좋은 것이기 때문에 알아놓으면 유용하게 쓰일 것 같다.

 

 

# 🥨 switch문의 exhaustive 한 분기 처리와 @unknown

swich문의 특징 중 하나는 exhaustive 해야 한다는 점이다. 그렇다면 왜 switch 문은 exhaustive ‘(하나도 빠뜨리는 것 없이) 철저한[완전한]’ 해야 하는가?

 

수많은 예외사항과 에러가 발생할만한 상황을 미리 방지하기 위해서 분기 처리 하지 않는 케이스가 없어야 하기 때문이다.

우리는 보통 switch문의 마지막에 분기처리한 케이스 외에 다른 경우의 수를 위해 default블록을 추가하는데 default 블록만 추가하면 안전할까? 복잡한 분기처리의 경우 개발자는 exhaustive 하게 처리되었는지 구분하기 힘들기 때문에 exhaustive하게 처리되었는지 알 수 있게 하는 키워드가 바로 @unknown이다.

 

스위치문은 열거형에 대한 분기처리에 특히 최적화되어 있기 때문에 열거형에 대한 예시로 설명하겠다.

 

 

LoginProvider이라는 열거형의 케이스가 세 가지 있을 경우

enum LoginProvider: String {
    case email
    case facebook
    case google
}


let userLogin = LoginProvider.email


switch userLogin {                
case .email:
    print("이메일 로그인")
case .facebook:
    print("페이스북 로그인")
default:                         
    print("구글 로그인")
}

 

LoginProvider이라는 열거형의 케이스가 한 가지 추가됐을 경우

아래에 kakaotalk라는 케이스가 추가됐지만 switch문을 수정해주지 않으면 ‘구글 로그인’이 출력된다. default블록을 추가하는 것 만으로는 안전하지 않다는 것을 알 수 있는 것이다.

enum LoginProvider: String {
    case email
    case facebook
    case google
    case kakaotalk
}


let userLogin = LoginProvider.kakaotalk


switch userLogin {
case .email:
    print("이메일 로그인")
case .facebook:
    print("페이스북 로그인")
default:
    print("구글 로그인")
}


//구글 로그인

 

 

@unknown 키워드

위의 경우에서 나오는 오류를 해결하기 위해 열거형의 케이스가 추가될 때마다 switch 문을 찾아가서 exhaustive한지 확인하는 것은 비효율적이다. 이 대신, default 앞에 @unknown 키워드를 추가해 놓으면 switch문에서 열거형의 모든 케이스를 다루지 않는 경우, 경고를 통해 알려준다.

 

즉, @unknown 키워드는 개발자의 실수 가능성을 컴파일 시점에 알려주는 것!!이다. ("Switch must be exhaustive"경고 메시지)

enum LoginProvider: String {
    case email
    case facebook
    case google
    case kakaotalk
}


let userLogin = LoginProvider.kakaotalk


switch userLogin {
case .email:
    print("이메일 로그인")
case .facebook:
    print("페이스북 로그인")
@unknown default:
    print("구글 로그인")
}