[Swift] Generic과 SubType
문제 상황?
protocol ViewProtocol {}
class ParentModel {}
class ChildModel: ParentModel {}class ParentView<T:ParentModel>: ViewProtocol {}
class ChildView: ParentView<ChildModel> {}
ChildModel
은ParentModel
을 inheritParentView
의 GenericTypeT
는ParentModel
을 inheritChildView
는ParentView
를 inherit. 이때ParentView
의 GenericType은ChildModel
자 이제 ChildView
를 생성해서 typeCheck를 해봅시다.
let childView = ChildView()
typeCheck(childView)func typeCheck(_ view: ViewProtocol) {
print(view is ParentView)
print(view is ParentView<ChildModel>)
}
여러분도 같이 결과를 생각해 보아요
정답은.. 두둥
view is ParentView // false
view is ParentView<ChildModel> // true
제가 결과를 보고 든 생각은 이랬어요.
엥..??? 아니 왜 첫번째게
false
임?ChildView
는ParentView
를 상속하고 있으니까true
여야 하지 않나?
ParentView
를 상속받을때 설정한 GenericType은ChildModel
인데.. 얘는ParentModel
상속하고 있는 애니까 어차피 문제 없을테고???
비슷한 다른 상황을 하나 더 살펴 볼게요.
protocol BaseProtocol {}
struct BaseModel: BaseProtocol {}
struct SomeType<T> {}var items: Array<SomeType<BaseProtocol>> = []
let item = SomeType<BaseModel>()
- items의 타입은
SomeType<BaseProtocol>
의Array
- item의 타입은
SomeType<BaseModel>
BaseModel
은BaseProtocol
을 conform
이 상황에서 아래처럼 append 한다면?
items.append(item)
이런 에러 메시지와 함께 에러가 납니다.
Cannot convert value of type 'SomeType<BaseModel>' to expected argument type 'SomeType<BaseProtocol>'
아니 BaseModel
이 BaseProtocol
을 따르고 있는데도 에러가 난다구? 도대체 왜???
원인
이는 Swift의 Generic이 invariant 하기 때문에 생긴 일입니다.
🙋🏻♀️ invariant가 뭔데요?
😎 covariant하지 않다는 뜻이요!
🤦🏻♀ covariant는 또 뭔데요;;;
자 용어부터 짚고 갑시다.
Variance
- T 는 element, <T> 는 element를 포함하는 컴포넌트
- A → B 이면 A 는 B 의 서브 타입
- 서브 타입은 상속만이 아니라 더 넓은 개념인 Subtyping 을 의미
이런 상황에서 Variance란 아래와 같이 정의할 수 있습니다.
element 간의 sub type 관계가 컴포넌트간의 sub type 관계에 영향을 주는 정도
그리고 영향도에 따라 다음과 같이 나뉩니다.
- Invariant (무공변성) : T → T’ 일 때 <T>, <T’> 는 서로 별개의 타입이면 <T> 는 Invariant 하다.
- Covariant (공변성) : T→ T’ 일 때 <T> → <T’> 가 성립하면 <T> 는 covariant 하다.
- Contravariant (반공변성) : T → T’ 일 때 <T> ← <T’> 가 성립하면 <T> 는 contravariant 하다.
아까 Swift의 Generic이 invariant하다고 했죠? 이는 즉 T
랑 U
가 subtyping 같은 관계일지라도 어쨌든 T != U
이면 X<T>
는 X<U>
와 완전 무관하다는 의미입니다.
이렇게 <T>
가 기본적으로 invariant한 이유는 타입 안정성을 컴파일 타임에 보장하기 위해서라고 합니다.
만약 Parameterized Type(<T>
) 에 subtyping 이 허용된다고 가정해봅시다. 그렇게 되면 어떤 타입의 파라미터가 넘어올지 컴파일 타임에 미리 아는 것이 불가능하고, 예외 발생을 막기위해 지속적으로 타입 체크가 필요합니다. 즉, 제네릭의 장점을 전혀 취할 수 없게 됩니다.
또한 subtyping 이 허용되면 제네릭 타입과 관련된 paramter 및 setter 등을 정의하는 generic reference type을 완전히 깨트릴 수 있다..고하는데 더 자세한건 아래 출처들을 참고해주세여
마무리
튼 T → U
인 사실과 관계없이 X<T>
와 X<U>
는 완전 다른 것!이 Swift Generic의 특성이군요.
protocol ViewProtocol {}
class ParentModel {}
class ChildModel: ParentModel {}class ParentView<T:ParentModel>: ViewProtocol {}
class ChildView: ParentView<ChildModel> {}let childView = ChildView()
typeCheck(childView)func typeCheck(_ view: ViewProtocol) {
print(view is ParentView) //false
print(view is ParentView<ChildModel>) //true
}
제가 view is ParentView
결과가 false
인걸 보고 이렇게 생각했다고 했쥬?
ChildView
가ParentView
를 상속받을때 설정한 GenericType은ChildModel
인데.. 얘는ParentModel
상속하고 있는 애니까 어차피 문제 없을테고???
문제가 없는게 아니라 얘 땜에 문제가 되는거였네요 ㅎ
다시 한번 말하지만 Swift Generic은 Invariant하기 때문이죠~
ChildModel → ParentModel
인 사실과 관계없이ParentView<ChildModel>
와ParentView<ParentModel>
는 완전 다른 것!
오키오키! 그럼 행복한 주말 되시길~ 즐주행주~~! ㅃㅏ이!