이전 포스트(Generic vs Opaque Type)에서 Generic과 Opaque Type의 차이점에 대해서 알아보았는데,
Opaque Type이 Protocol 앞에 some
을 붙인 모습이란 것을 알 수 있었을 것이다.
그렇다면 왜 some
을 붙이고 이로인해 만들어지는 Protocol과 Opaque Type의 차이점에 대해서 알아보자.
Protocol과 Opaque Type의 가장 큰 차이점은 Type Identity의 여부이다.
이로 인해서 실제 사용시 다음과 같은 차이가 발생한다.
다음과 같은 코드가 있다.
protocol Quadrilateral {
var width: Float { get set }
var height: Float { get set }
}
struct Square: Quadrilateral {
var width: Float
var height: Float
}
struct Rectangle: Quadrilateral {
var width: Float
var height: Float
}
struct InversedRectangle: Quadrilateral {
var width: Float
var height: Float
init(rectangle: Quadrilateral) {
self.width = rectangle.height
self.height = rectangle.width
}
}
func getProtoQuadrilateral<T: Quadrilateral>(_ shape: T) -> Quadrilateral {
if shape is Square { return shape }
return InversedRectangle(rectangle: shape)
}
// Error: Function declares an opaque return type, but the return statements in its body do not have matching underlying types
func getOpaqueQuadrilateral<T: Quadrilateral>(_ shape: T) -> some Quadrilateral {
if shape is Square { return shape }
return InversedRectangle(rectangle: shape)
}
잠시 코드를 설명하자면,
높이(height
)와 너비(width
) 변수를 가지는, 사각형을 뜻하는 Quadrilateral
Protocol이 존재한다.
그리고 이 Protocol을 준수하는 다음과 같은 세가지 structure
가 존재한다.
Square
: 정사각형Rectangle
: 직사각형InversedRectangle
: 입력받은 Quadrilateral
의 높이와 너비를 뒤바꾼 Quadrilateral
아래에는 두개의 function getProtoQuadrilateral
와 getOpaqueQuadrilateral
이 있는데,
두 function 모두 Quadrilateral
를 parameter로 받아서 Sqaure
일 경우 그대로 return하고,
Square
이 아닐 경우 해당 Quadrilateral
을 뒤집은 InversedRectangle
을 return 한다.
이떄 OpaqueType을 return하는 getOpaqueQuadrilateral
에서 error가 발생하는 것을 확인 할 수 있는데,
이는 getOpaqueQuadrilateral
이 상황에 따라서 Squre
또는 InversedRectangle
을 return 할 수 있기 때문이다.
OpaqueType은 Type Identity를 가지고 있고, 이 때문에 함수 return시에 하나의 구체 Type만을 return해야한다.
즉, Quadrilateral
을 만족하는 한 가지 Type의 return만 가능한 것이다.
반면, Protocol은 Quadrilateral
을 채택하는 모든 Type을 return 할 수 있다.
요약
OpaqueType은 하나의 구체 Type만을 return.
Protocol은 모든 Type을 return.
위에서 Protocol은 Type Identity를 가지지 않고, Opaque Type은 Type Identity를 가진다고 했다.
Type Identity의 여부는 Self
및 associatedType
의 추론 조건중 하나이다.
즉, Type Identity를 가지지 않게 되면 Self
및 associatedType
을 추론할 수 없으므로 이를 return 값으로 사용할 수 없다.
아래에서 조금 더 자세히 살펴보자.
Equatable
의 ==
Operator는 (Self, Self)를 parameter로 받는다.
Self
는 보통 Protocol을 채택하는 어떠한 구체적인 Type과 매칭되지만,
앞서 말했듯이 Protocol은 Type에대한 Identity를 가지고 있지 않으므로 Self
를 사용할 수 없다.
즉, protocol은 ==
Operator을 사용할 수 없다.
다음은 associatedType
이 적용된 경우 Protocol을 return 할 수 없는 이유이다.
다음과 같은 코드가 있다.
protocol Container {
associatedtype dataType
var dataArray: [dataType] { get set }
}
struct IntContainer: Container {
var dataArray: [Int] = [1, 2, 3, 4, 5]
}
struct StringContainer: Container {
var dataArray: [String] = ["One", "Two", "Three", "Four", "Five"]
}
// Error: Protocol 'Container' can only be used as a generic constraint because it has Self or associated type requirements
let containerProto: Container = IntContainer()
// OK
let containerOpaque: some Container = IntContainer()
let
containerProto
는 Container
protocol을 값으로 받으려고 하는데,
protocol은 Type Identity가 없으므로 외부에서 associatedType
을 추론하기 위한 충분한 정보가 없다.
따라서 protocol을 반환 할 수 없으므로 error가 발생한다.
반면 Opaque Type을 받는 containerOpaque
는 Type Identity를 가지고 있으므로 AssociatedType
을 추론할 수 있다.
즉, return값으로 사용할 수 있게 된다.
요약
associatedType 및 Self를 사용하고자 하는 경우, Opaque Type을 return하여 사용하여야 한다.
https://docs.swift.org/swift-book/LanguageGuide/OpaqueTypes.html