본문 바로가기
카테고리 없음

18. 제네릭과 프로토콜을 함께 사용하는 방법

by swifttt 2025. 4. 7.

 

 

Swift에서 제네릭(Generic)과 프로토콜(Protocol)은 각각 강력한 추상화 도구입니다.
제네릭은 타입에 의존하지 않는 유연한 코드를 가능하게 하며, 프로토콜은 공통된 인터페이스를 정의함으로써 다양한 타입을 하나로 묶을 수 있게 합니다.

이 둘을 함께 사용하는 경우, 더 강력하고 확장성 있는 코드를 작성할 수 있습니다.
이 글에서는 제네릭 함수나 타입에서 프로토콜 제약을 걸거나, associatedtype과 함께 사용하는 다양한 패턴을 소개합니다.

1. 제네릭에 프로토콜 제약을 거는 기본 문법

func printDescription<T: CustomStringConvertible>(_ value: T) {
    print(value.description)
}

위 함수는 CustomStringConvertible 프로토콜을 채택한 타입만 허용합니다.

printDescription("Swift")        // Swift
printDescription(123)            // 123
printDescription([1, 2, 3])      // [1, 2, 3]

2. 여러 개의 프로토콜 제약

func compare<T: Equatable & Comparable>(_ a: T, _ b: T) -> Bool {
    return a == b
}

compare(3, 3)     // true
compare("a", "b") // false

이러한 방식은 where절을 통해 더 유연하게 확장할 수 있습니다.

3. where 절을 통한 고급 제약

func areAllEqual<T>(_ items: [T]) -> Bool where T: Equatable {
    guard let first = items.first else { return true }
    return items.dropFirst().allSatisfy { $0 == first }
}

areAllEqual([1, 1, 1]) // true
areAllEqual([1, 2, 1]) // false

where절은 프로토콜 준수 외에도 연관 타입의 제약 등 다양한 조건 설정에 활용됩니다.

4. 프로토콜 내부에서 제네릭을 사용하는 방법

protocol Transformer {
    associatedtype Input
    associatedtype Output

    func transform(_ input: Input) -> Output
}

이러한 형태는 일반적으로 타입 지우기(type erasure)와 함께 사용됩니다.

5. 프로토콜 타입과 제네릭의 구분

간혹 다음과 같은 형태를 혼동하는 경우가 있습니다.

func logItems(_ items: [CustomStringConvertible]) {
    items.forEach { print($0.description) }
}

이 함수는 CustomStringConvertible을 타입으로 사용합니다. 하지만 모든 요소가 동일한 구체 타입일 필요는 없습니다.
반면 다음 제네릭 함수는 모든 요소가 동일한 타입이면서, 해당 프로토콜을 채택해야 합니다.

func logItems<T: CustomStringConvertible>(_ items: [T]) {
    items.forEach { print($0.description) }
}

제네릭은 더 구체적이고, 타입 안정성을 확보하는 데 유리합니다.

마무리

제네릭과 프로토콜을 함께 사용하는 것은 Swift의 타입 시스템을 최대한 활용하는 방식입니다.
제네릭을 통해 유연성을 확보하고, 프로토콜을 통해 일관된 인터페이스를 정의할 수 있습니다.
특히 where절과 associatedtype을 함께 활용하면 복잡한 추상화도 간결하게 표현할 수 있으며,
실무에서 재사용성과 유지 보수성이 뛰어난 코드를 작성하는 데 큰 도움이 됩니다.