sanichdaniel의 iOS 개발 블로그
Type Erasure 본문
요약
프로토콜을 제너릭하게 쓰고 싶으면 associatedtype을 사용하면 된다.
associatedtype은 프로토콜이 채택한 순간에 구체 타입으로 교체된다.
하지만 프로토콜을 채택하지 않고 그냥 함수의 인자, 리턴타입, 변수의 타입, 콜렉션의 타입으로 쓰는 경우 컴파일타임에 associatedtype의 구체 타입이 결정이 안되어 컴파일 에러가 난다.
이런경우에는 type-erasure wrapper-class를 만들어서 해결할수 있다.
애플 디벨로퍼 문서를 읽다가 Type-Erasing Wrappers를 발견했다.
무엇인지 알아보기 위해 공부해보았다.
Protocols & Associated Types
type-erasure에 앞서 protocol과 associated type에 대해 알아야한다.
associated type은 프로토콜과 제너릭(associatedtype)의 결합이다.
아래 예시에서 CarFactoryProtocol을 채택한다면 여러 타입의 차를 찍어낼수 있다.
protocol CarFactoryProtocol {
associatedtype CarType
func produce() -> CarType
}
struct ElectricCar {
let brand: String
}
struct PetrolCar {
let brand: String
}
struct TeslaFactory: CarFactoryProtocol {
typealias CarType = ElectricCar
func produce() -> TeslaFactory.CarType {
print("producing tesla electric car ...")
return ElectricCar(brand: "Tesla")
}
}
struct BMWFactory: CarFactoryProtocol {
typealias CarType = ElectricCar
func produce() -> BMWFactory.CarType {
print("producing bmw electric car ...")
return ElectricCar(brand: "BMW")
}
}
자 이제 여러타입의 전기자동차를 생산하는 팩토리들의 어레이를 만들고 싶다
또는 팩토리를 인자로 받는 함수를 만들고 싶다.
let KiaFactory: CarFactoryProtocol
/// or
let electricCarFactories: [CarFactoryProtocol]
/// or
func getCarFactory(factory: CarFactoryProtocol) { }
하지만 아래와 같은 에러가 발생한다
Protocol 'CarFactoryProtocol' can only be used as a generic constraint because it has Self or associated type requirements
원인
Generic Type Parameters
placeholder 타입으로, struct, class, enum에서 사용된다.
컴파일 타임에 placeholder은 구현 타입으로 교체된다.
Associated Type
protocol에서 사용되는 placeholder이다.
하지만 기존의 generic type parameter와 가장 큰 차이점으로는 placeholder가 교체되는 시점이다.
구현 타입이 프로토콜을 채택한 시점에 associatedtype의 구현 타입이 채워집니다.
프로토콜을 채택해야 associatedtype을 제공하니, 만약 프로토콜이 함수의 인자, 리턴 타입, 변수의 타입, 콜렉션의 타입으로 사용되는 경우, 컴파일러는 associatedtype(플레이스홀더)의 구체 타입을 알 수가 없다.
컴파일러는 컴파일타임에 항상 구체 타입을 알아야한다.
Type erasure to the rescue
Wrapper 클래스가 필요하다
랩퍼 클래스는 Factory 프로토콜을 채택한 인스턴스만 인자로 받아들인다.
또한 그 Factory 프로토콜의 CarType은 AnyCarFactory 타입 패러미터의 CarType과 같아야한다.
struct AnyCarFactory<CarType>: CarFactoryProtocol {
private let _produce: () -> CarType
init<Factory: CarFactoryProtocol>(_ carFactory: Factory) where Factory.CarType == CarType {
_produce = carFactory.produce
}
func produce() -> CarType {
return _produce()
}
}
factories에는 TeslaFactory, BMWFactory 같은 타입정보가 지워져있기에 type-erasure이라한다.
보통 통상적으로는 랩퍼클라쓰는 Any가 앞에 붙는다
let factories = [AnyCarFactory(TeslaFactory()), AnyCarFactory(BMWFactory())]
factories.map() { $0.produce() }
// Output:
// producing tesla electric car ...
// producing bmw electric car ...
한계
추가적으로 보일러 플레이트 코드를 작성해야한다.
추가적으로 알아두면 좋은것
opaque type은 type identity를 간직하기에, assoicated type의 구체 타입을 추측할수 있다
type-erasure말고 opaque type으로 해결할수 없는지도 고민해보자
출처
krakendev.io/blog/generic-protocols-and-their-shortcomings
academy.realm.io/posts/tryswift-gwendolyn-weston-type-erasure/
www.donnywals.com/understanding-opaque-return-types-in-swift-5-1/
www.bignerdranch.com/blog/breaking-down-type-erasure-in-swift/
swiftrocks.com/using-type-erasure-to-build-a-dependency-injector-in-swift
'swift' 카테고리의 다른 글
Opaque Types (0) | 2020.09.29 |
---|---|
enum vs protocol as UseCase (0) | 2020.09.28 |
initializer (0) | 2020.09.26 |
Self Type (0) | 2020.09.05 |
type(of:) (0) | 2020.09.05 |