이번 포스트에서는 Swift에서 조건을 추가할때 사용하는 where
문법에 대해 알아보려고 한다.
where
이라는 표현은 사실 SQL을 사용해 봤다면 특정 레코드의 조건을 만족하는 데이터에 대해 SELECT
, UPDATE
등의 명령을 수행하기 위해 사용한 경험이 있을 수 있다.
SELECT * FROM CLIENT WHERE AGE > 20;
Swift에서도 비슷한 흐름으로 조건을 추가하거나, 타입에 대한 제한을 가하기 위해 where
문법을 사용할 수 있다.
구체적으로 다시 설명하자면 Swift에서는 패턴 매칭에 조건을 추가하거나, 제네릭이나 프로토콜 익스텐션 등에서 타입에 제약을 주기 위해 사용할 수 있다.
먼저 패턴에서 사용되는 where
절의 형태를 살펴보자
아래의 코드는 Tuple Pattern과 함께 where
절을 사용하여 포인트 좌표가 몇 사분면에 위치하는지 조건에 따라 출력하는 예제이다.
let point: (Int, Int) = (-3, 2)
switch point {
case let (x, y) where x > 0 && y > 0:
print("(\(x), \(y)): 1사분면")
case let (x, y) where x < 0 && y > 0:
print("(\(x), \(y)): 2사분면")
case let (x, y) where x < 0 && y < 0:
print("(\(x), \(y)): 3사분면")
case let (x, y) where x > 0 && y < 0:
print("(\(x), \(y)): 4사분면")
case let (_, _):
print("(\(point.0), \(point.1)): 선의 경계")
}
//(-3, 2): 2사분면
Tuple Pattern으로 매칭 후 where
절을 사용하여 조건을 추가 함으로서 조건에 해당하는 case
문을 실행할 수 있다.
추가적으로 Optional Pattern에서 사용되는 where
절 예시를 한가지만 더 살펴보자
let heights: [Int?] = [nil, 178, 163, 172, nil, 181, nil]
for case .some(let value) in heights where value > 170 {
print(value)
}
//178
//172
//181
Optional Pattern으로 값이 nil
이 아닌 경우, 추가적으로 where
절로 조건을 통해 그 값이 170보다 큰 경우 해당 값을 출력하도록 제약을 줄 수 있다.
이외에도 Swift의 다양한 패턴에 where
절을 사용할 수 있다.
프로토콜 익스텐션에 where
절을 사용하면 프로토콜을 준수하는 타입 중 특정 프로토콜을 함께 준수하는 타입만 익스텐션으로 기능을 확장 하도록 제약을 줄 수 있다.
코드로 표현하자면 다음과 같다.
protocol A {}
extension A where Self: B, Self: C {}
위 코드의 의미는 프로토콜 A
를 준수하는 타입에서 B
프로토콜과 C
프로토콜을 함께 준수하는 타입에 한해서 extesion으로 기능을 확장한다는 의미이다.
아래의 코드에서 PrintCharacter
프로토콜에 익스텐션을 사용하여 CustomStringConvertible
프로토콜과 Collection
프로토콜을 동시에 준수하는 타입에 대해서 문자열의 각 문자를 하나씩 출력 하도록 하는 함수를 추가 하도록하는 예제이다.
protocol PrintCharacter {
func printCharacter()
}
extension String: PrintCharacter {}
//extension Int: PrintCharacter {}
extension PrintCharacter where Self: CustomStringConvertible, Self: Collection {
func printCharacter() {
for char in description {
print(char)
}
}
}
String("Hello").printCharacter()
//H
//e
//l
//l
//o
//Int(19).printCharacter()
Int
타입은 CustomStringConvertible
프로토콜과 Collection
프로토콜을 동시에 준수하지 않기 때문에 Int
타입에 적용하려고 하면 에러가 발생한다.
제네릭에서 타입 매개변수와 연관 타입에 where절을 사용하여 조건을 추가할 수 있다.
아래의 코드는 제네릭의 타입 매개변수에 where
절을 사용하여 조건을 추가하는 예제이다.
func compareTwoItems<T1, T2>(first: T1, second: T2) where T1: Comparable, T2: Comparable, T1 == T2 {
if first > second {
print("\(first)(이)가 \(second)보다 큽니다.")
} else if first < second {
print("\(second)(이)가 \(first)보다 큽니다.")
} else {
print("두 값이 같습니다.")
}
}
compareTwoItems(first: 3, second: 5) //5(이)가 3보다 큽니다.
compareTwoItems(first: "b", second: "a") //b(이)가 a보다 큽니다.
함수 compareTwoItems
는 타입 매개변수 T1
, T2
를 가지며 where
절을 사용하여 T1
과 T2
는 Comparable
프로토콜을 준수하는 타입이면서 두 타입 매개변수가 같아야 한다는 제약을 추가하였다.
따라서 Comparable
프로토콜을 준수하는 Int
, Double
, String
, Date
등의 값을 매개변수로 전달하여 함수를 호출할 수 있다.
아래 코드는 프로토콜에서 사용하는 연관 타입에 where
절을 사용하여 조건을 추가하는 예제이다.
protocol IntegerStack {
associatedtype ItemType where ItemType: BinaryInteger
var items: [ItemType] {get}
mutating func push(item: ItemType) -> Void
mutating func pop() -> ItemType
subscript(i: Int) -> ItemType {get}
subscript<Indices: Sequence>(indices: Indices) -> [ItemType] where Indices.Iterator.Element == Int {get}
}
연관 타입 ItemType
은 BinaryInteger
프로토콜을 준수하는 타입으로 Int
, UInt
등의 타입이 올 수 있다.