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

16. 프로토콜 타입과 타입 지우기(Type Erasure)

by swifttt 2025. 4. 6.

 

 

Swift에서 프로토콜은 타입 간의 공통된 인터페이스를 정의하기 위해 사용됩니다.
이러한 프로토콜은 일반 타입처럼 변수나 매개변수의 타입으로도 사용 가능한데, 이를 프로토콜 타입(protocol type)이라고 부릅니다.

하지만 모든 프로토콜을 일반 타입처럼 사용할 수 있는 것은 아니며, 특히 Self 요구나 associated type이 있는 경우에는 타입 지우기(Type Erasure)가 필요합니다.

이 글에서는 프로토콜 타입의 기본 개념과 함께, 타입 지우기의 개념과 구현 방법을 살펴보겠습니다.

1. 프로토콜 타입이란?

프로토콜을 타입처럼 사용할 수 있으며, 이를 통해 여러 타입을 하나로 묶을 수 있습니다.

protocol Drawable {
    func draw()
}

struct Circle: Drawable {
    func draw() {
        print("원을 그립니다.")
    }
}

struct Square: Drawable {
    func draw() {
        print("사각형을 그립니다.")
    }
}

let shapes: [Drawable] = [Circle(), Square()]
shapes.forEach { $0.draw() }

shapes는 서로 다른 타입(Circle, Square)이지만, Drawable이라는 공통 프로토콜로 타입이 통일되어 있습니다.

2. associatedtype과 프로토콜 타입의 제약

associatedtype을 포함한 프로토콜은 컴파일러가 구체 타입을 알 수 없기 때문에 프로토콜 타입으로 직접 사용할 수 없습니다.

protocol Container {
    associatedtype Item
    var items: [Item] { get }
}

이 프로토콜을 직접 타입으로 쓰면 오류가 발생합니다.

// 오류 발생: 'Container'는 'associatedtype'을 포함하고 있으므로 직접 사용할 수 없습니다.
func printContainer(_ c: Container) {
    print(c.items)
}

이러한 경우 타입 지우기(type erasure)를 사용해야 합니다.

3. 타입 지우기(Type Erasure)란?

타입 지우기는 프로토콜의 구체 타입을 감추고, 외부에는 프로토콜 타입처럼 보이게 하는 기술입니다.
이를 통해 associatedtype이 있는 프로토콜도 일반 타입처럼 사용할 수 있습니다.

4. 예제: 타입 지우기 구현

먼저 프로토콜을 정의합니다.

protocol Printable {
    associatedtype Content
    func printContent(_ content: Content)
}

그리고 이 프로토콜을 채택하는 여러 타입을 정의합니다.

struct StringPrinter: Printable {
    func printContent(_ content: String) {
        print("문자열 출력: \(content)")
    }
}

struct IntPrinter: Printable {
    func printContent(_ content: Int) {
        print("정수 출력: \(content)")
    }
}

Printableassociatedtype이 있으므로 [Printable] 같은 배열을 만들 수 없습니다.
이를 해결하기 위해 타입 지우기를 구현합니다.

5. AnyPrintable: 타입 지우기 래퍼 구현

class AnyPrintable<T>: Printable {
    private let _print: (T) -> Void

    init<P: Printable>(_ printer: P) where P.Content == T {
        _print = printer.printContent
    }

    func printContent(_ content: T) {
        _print(content)
    }
}

이제 다양한 프린터를 하나의 배열로 사용할 수 있습니다.

let printers: [AnyPrintable<String>] = [
    AnyPrintable(StringPrinter())
    // AnyPrintable(IntPrinter()) // 오류: 타입 불일치
]

printers.forEach { $0.printContent("Hello") }

타입이 지워졌기 때문에 외부에서는 Printable이라는 공통 인터페이스만 인식하고, 내부 구현은 알 수 없습니다.

마무리

Swift에서 프로토콜 타입은 다양한 객체를 통합적으로 다룰 수 있도록 해주는 강력한 기능입니다.
하지만 associatedtype이나 Self 요구가 포함된 프로토콜은 제한이 있으므로, 이때 타입 지우기를 통해 유연하게 처리할 수 있습니다.
타입 지우기를 적절히 활용하면 제네릭과 프로토콜 기반 설계를 더 유연하게 확장할 수 있습니다.