🔥 제네릭 Where절
Swift의 제네릭은 코드의 재사용성과 유연성을 크게 향상시켜 줍니다. 제네릭을 사용하면 타입에 구애받지 않고 다양한 타입에 대해 동작하는 함수나 타입을 정의할 수 있습니다. 이런 제네릭의 강력한 기능 중 하나가 바로 "제네릭 where절"이에요.
제네릭 where절을 사용하면 제네릭 타입 매개변수나 연관 타입에 대한 추가적인 제약 조건을 명시할 수 있어요. 이를 통해 제네릭 코드의 유연성을 유지하면서도 타입 안정성을 확보할 수 있죠.
제네릭 Where절의 문법
제네릭 where절은 where 키워드 뒤에 제약 조건을 명시하는 형태로 작성됩니다. 제약 조건에는 다음과 같은 내용들이 포함될 수 있어요:
- 연관 타입이 특정 프로토콜을 준수해야 한다는 것
- 타입 매개변수나 연관 타입 간의 동일성
제네릭 where절은 타입이나 함수 본문의 시작 중괄호({) 바로 앞에 위치하는데요, 아래는 제네릭 where절의 간단한 예시 코드예요:
func someFunction<T: SomeProtocol, U: SomeProtocol>(a: T, b: U) -> Bool where T.SomeType == U.SomeType, T.AnotherType: AnotherProtocol { // ... }swift
위 코드에서는 두 개의 타입 매개변수 T와 U가 모두 SomeProtocol을 준수해야 해요. 추가로 where 절을 통해 아래와 같은 제약 조건을 명시하고 있네요:
T의 연관 타입SomeType과U의 연관 타입SomeType이 동일해야 함T의 연관 타입AnotherType이AnotherProtocol을 준수해야 함
이처럼 제네릭 where절을 활용하면 타입 매개변수와 연관 타입에 대해 매우 세밀한 제어가 가능해집니다!
제네릭 Where절 활용 예제
제네릭 where절이 실제로 어떻게 활용될 수 있는지 좀 더 구체적인 예제 코드를 통해 알아볼까요?
아래 코드는 두 컨테이너가 동일한 아이템들을 동일한 순서로 담고 있는지 검사하는 제네릭 함수랍니다:
func allItemsMatch<C1: Container, C2: Container> (_ someContainer: C1, _ anotherContainer: C2) -> Bool where C1.Item == C2.Item, C1.Item: Equatable { // 두 컨테이너의 아이템 개수가 같은지 검사해요 if someContainer.count != anotherContainer.count { return false } // 각 아이템 쌍이 동등한지 검사합니다 for i in 0..<someContainer.count { if someContainer[i] != anotherContainer[i] { return false } } // 모든 아이템이 일치하므로 true 반환하네요 return true }swift
위 함수는 someContainer와 anotherContainer라는 두 개의 매개변수를 받아요. someContainer의 타입은 C1, anotherContainer의 타입은 C2인데, 이 C1과 C2는 함수가 호출될 때 결정될 컨테이너 타입들을 나타내는 타입 매개변수랍니다.
함수 선언부의 제네릭 where절을 통해 아래와 같은 제약 조건들을 설정하고 있어요:
C1은Container프로토콜을 준수해야 함C2도Container프로토콜을 준수해야 함C1의 연관 타입Item과C2의 연관 타입Item이 동일해야 함C1의 연관 타입Item은Equatable프로토콜을 준수해야 함
이런 제약 조건 덕분에 비록 서로 다른 컨테이너 타입이더라도 allItemsMatch 함수 내에서 두 컨테이너를 비교하는 것이 가능해집니다!
allItemsMatch 함수는 먼저 두 컨테이너의 아이템 개수가 동일한지 검사해요. 개수가 다르다면 두 컨테이너가 일치할 가능성이 없으므로 바로 false를 반환하죠.
개수가 같다면 for-in 루프를 통해 someContainer의 모든 아이템들을 순회하면서, 각 아이템이 anotherContainer의 같은 인덱스에 있는 아이템과 일치하는지 검사합니다. 만약 일치하지 않는 아이템 쌍이 발견되면 역시 false를 반환하게 되요.
루프가 끝날 때까지 불일치하는 아이템 쌍이 발견되지 않았다면, 두 컨테이너는 완전히 일치하는 것이므로 true를 반환하네요.
실제 사용 예시를 보면 제네릭 where절 덕분에 allItemsMatch 함수가 매우 유연하게 동작하는 것을 확인할 수 있어요:
var stackOfStrings = Stack<String>() stackOfStrings.push("uno") stackOfStrings.push("dos") stackOfStrings.push("tres") var arrayOfStrings = ["uno", "dos", "tres"] if allItemsMatch(stackOfStrings, arrayOfStrings) { print("All items match.") } else { print("Not all items match.") } // "All items match." 출력됩니다swift
Stack과 Array는 서로 다른 타입의 컨테이너지만, 둘 다 Container 프로토콜을 준수하고 동일한 타입의 값을 담고 있기 때문에 allItemsMatch 함수에 전달될 수 있어요. 그리고 실제로 두 컨테이너에 담긴 문자열 값들이 완전히 일치하기 때문에, allItemsMatch는 true를 반환하게 되는 거죠!
이처럼 제네릭 where절은 연관 타입과 타입 매개변수에 추가 제약을 부여함으로써 제네릭 코드의 표현력과 유연성을 크게 향상시켜 줍니다. 제네릭을 사용할 때 꼭 알아두면 좋을 기능이라 할 수 있겠네요.