Swift

Swift - where 절

호종이 2022. 2. 21. 19:43

스위프트의 where 절은 특정 패턴과 결합하여 조건을 추가하는 역할을 한다. where 절은 크게 두 가지 용도로 사용된다.

 

  • 패턴과 결합하여 조건 추가
  • 타입에 대한 제약 추가

즉 특정 패턴에 Bool 타입 조건을 지정하거나, 어떤 타입의 특정 프토코로 준수 조건을 추가하는 등의 기능이 있다. 예제를 통해서 알아보도록 하자

값 바인딩, 와일드카드 패턴과 결합한 where 절


let tuples : [(Int,Int)] = [(1,2),(1,-1),(1,0),(0,2)]

for tuple in tuples {
    switch tuple {
        //값 바인딩
        case let (x,y) where x==y : print("x==y")
        case let (x,y) where x == -y: print("x==-y")
        case let (x,y) where x>y:print("x>y")
        //와일드카드 패턴
        case (1,_): print("x==1")
        case (_,2): print("y==2")
        default: print("\(tuple.0), \(tuple.1)")
    }
}

값 바인딩, 또는 와일드 카드 패턴을 사용한 where 절로 조건을 추가할 수 있다. 튜플의 데이터를 x 와 y 상수로 바인딩하고, where 절을 사용해 x 와 y 를 비교하는 것을 볼 수 있다. where 절 뒤에는 Bool 타입의 표현식이 오면 된다. where 절을 사용해 값을 바인딩하고도 추가적인 조건을 통해 switch case 문에 접근할 수 있다. 만약 where 절이 없다면 코드는 다음과 같이 작성할 수 있을 것이다.

for tuple in tuples {
    switch tuple {
        case let (x,y):
            if x == y {
                print("x==y")
            } else if x == -y {
                print("x==-y")
            } else if x>y {
                print("x>y")
            }
            
    }
}

딱 그냥 보기에도 코드가 좀 더 복잡해진 것을 볼 수 있다. 이처럼 where 절을 사용하면 효과적으로 코드를 줄이고 가독성을 늘릴 수 있다. 

 

타입 캐스팅과 where 


where 절을 타입캐스팅 패턴과 결합할 수 있다.

let anyValue : Any = "ABC"

switch anyValue {
    //value 로 값을 바인딩하고, 타입 검사
    case let value where value is String: print("value is String")
    case let value where value is Int: print("value is Int")
    case let value where value is Float :print("value is Float")
    default: print("value is unknown type")
}

var things : [Any] = [0,0.0,"321",42,(3.0,5.0)]
for any in things {
    switch any {
        //값을 바인딩하고, 타입 검사 및 캐스팅
        case let someInt where someInt is Int : print("\(someInt) is Int")
        case let someDouble where someDouble is Double : print("\(someDouble) is Double")
        case let someString where someString is String : print("\(someString) is String")
        case let (double1,double2) as (Double,Double) : print("value is tuple")
        default: print("\(any) is unknown type")
    }
}

//출력 결과
value is String
0 is Int
0.0 is Double
321 is String
42 is Int
value is tuple

또 where 절은 표현 패턴과도 결합할 수 있다.

 

var point : (Int, Int) = (1,2)

switch point {
    case (0,0) : print("원점")
    case (-2...2,-2...2) where point.0 != 1 : print("\(point) 는 원점과 가깝다.")
    default : print("\(point)")
}

//출력 결과
(1,2)

Protocol Extension 와 where


프로토콜 익스텐션에 where 절을 사용하면 해당 익스텐션이 특정 프로토콜을 준수하는 타입에만 적용될 수 있도록 제약을 줄 수 있다. 다시 말해 익스텐션이 적용된 프로토콜을 준수하는 타입 중 where 절 뒤에 제시되는 프로토콜도 준수하는 타입만 익스텐션이 적용되도록 제약을 줄 수 있다. 

protocol ToString {
    func toString() -> String
}

extension Int : ToString {
    
}

extension UInt: ToString {
    
}

extension Double : ToString {
    
}

extension ToString where Self : FixedWidthInteger,Self :  SignedInteger {
    func toString() -> String {
        "FixedWidthInger, SignedInteger 프로토콜 준수 타입 \(type(of:self))"
    }
}

extension ToString {
    func toString() -> String {
        "그 외 타입! \(self)"
    }
}

let intValue = 5
let intValue2 = 10
let uInt : UInt = 291239012
let doubleValue = 29.0123

print(intValue.toString()) //FixedWidthInteger, SignedInteger 프로토콜 준수 타입 Int
print(intValue2.toString()) //FixedWidthInteger, SignedInteger 프로토콜 준수 타입 Int
print(uInt.toString()) //그 외 타입! 291239012
print(doubleValue.toString()) //그 외 타입! 29.0123

Int, UInt, Double 타입에 toString() 메소드를 추가하기 위해 ToString 이란 프로토콜을 생성하고, 익스텐션으로 각 타입이 ToString 프로토콜을 준수하게 만들었다. 그리고 where 절을 사용해 Int 타입에 적용되는 toString 함수와, 그 외 ToString 프로토콜을 준수하는 모든 타입에 사용되는 toString() 메소드를 익스텐션을 사용해 구현하였다. 

타입 매개변수나 연관 타입의 타입 제약 추가


타입 매개변수와 연관 타입의 제약을 추가하는 데 where 절을 사용하기도 한다. 제네릭 함수의 반환 타입 뒤에 where 절을 포함하면 타입 매개변수와 연관 타입에 요구사항을 추가할 수 있다.

func doubled<T>(integerValue : T) -> T where T : BinaryInteger {
    return integerValue * 2
}

//위 함수와 동일하다. 따라서 redeclaration 에러가 뜬다
//func doubled<T : BinaryInteger>(integerValue : T) -> T {
//    return integerValue * 2
//}


func prints<T,U>(first:T,second:U) where T : CustomStringConvertible, U:CustomStringConvertible {
    print(first)
    print(second)
}

func printValue<T>(value:T) where T : ToString {
    print(value.toString())
}