질문의 역사: 타입 지워짐(type erasure)은 연관 타입(associated type) 또는 Self 요구 사항이 있는 프로토콜의 제한에 대한 답으로 스위프트에서 나타난 패턴입니다. 프로토콜에 연관된 타입이 포함되어 있는 경우, 컴파일러가 특정 구현을 알지 못하기 때문에 타입 지워짐 없이 프로토콜을 타입으로 직접 사용할 수 없습니다. 데이터 컨테이너, 컬렉션, 스트림을 생성할 때 추상화가 중요하므로 종종 이 문제에 직면하게 됩니다.
문제: 연관 타입을 가진 프로토콜이 있다면, 예를 들어:
protocol Animal { associatedtype Food func eat(_ food: Food) }
Animal 타입의 변수를 선언할 수 없습니다. 우리는 서로 다른 구체적인 타입에 접근할 수 있도록 해주는 "지우개 래퍼" 타입이 필요합니다:
let zoo: [any Animal] // 컴파일 오류
해결책: 타입 지워짐은 래퍼(예: 박스 패턴 또는 struct AnyXxx)를 사용하여 구현되며, 구체적인 타입의 세부사항을 숨기고 단일 인터페이스를 제공합니다:
struct AnyAnimal<F>: Animal { private let _eat: (F) -> Void init<A: Animal>(_ base: A) where A.Food == F { _eat = base.eat } func eat(_ food: F) { _eat(food) } }
이제 같은 Food를 가진 서로 다른 Animal 구현을 저장할 수 있습니다:
let animals: [AnyAnimal<Grass>] = [AnyAnimal(Cow()), AnyAnimal(Sheep())]
주요 특징:
타입 지워짐 없이 연관 타입이 있는 프로토콜을 속성이나 배열 타입으로 사용할 수 있을까요?
아니요, 불가능합니다. 컴파일러는 모든 연관 타입을 구체화할 것을 요구합니다. 예를 들어, 다음과 같은 표현은 오류를 발생시킵니다:
let array: [Animal] // 오류: 'Animal'은 제네릭 제약으로만 사용될 수 있습니다.
타입 지워짐이 상속을 대체할 수 있을까요?
아니요, 타입 지워짐은 상속을 완전히 대체하는 것은 아닙니다. 그것은 연관 타입이 있는 프로토콜을 위한 추상화 문제를 해결하지만, 상속은 클래스 간의 공통 논리를 구현하고 코드를 재사용하기 위해 사용됩니다.
타입 지워짐 래퍼 내부에서 프로토콜의 모든 메서드와 속성을 구현해야 하나요?
네, 반드시 해야 합니다. 타입 지워짐은 래퍼가 프로토콜의 외부 인터페이스를 완전히 반복할 때만 작동하며, 그렇지 않으면 일부 기능이 사용할 수 없게 됩니다.
실제 프로젝트에서 모든 데이터 소스 작업이 타입 지워짐을 통해 수행되며, 연관 타입이 필요없는 경우에서도 사용되고 있습니다. 코드를 유지보수하기가 어려워지고 새로운 개발자들이 혼란스러워합니다.
장점:
단점:
타입 지워짐은 다양한 로딩 전략(네트워크, 로컬)을 캡슐화하는 데이터 소스의 추상화에만 사용되며, 각 전략은 고유한 타입을 가집니다. 나머지 코드가 투명합니다.
장점:
단점: