Protocol vs Opaque Type

Minseok, Kim·2022년 4월 13일
0

이전 포스트(Generic vs Opaque Type)에서 Generic과 Opaque Type의 차이점에 대해서 알아보았는데,
Opaque Type이 Protocol 앞에 some을 붙인 모습이란 것을 알 수 있었을 것이다.
그렇다면 왜 some을 붙이고 이로인해 만들어지는 Protocol과 Opaque Type의 차이점에 대해서 알아보자.

차이점

Protocol과 Opaque Type의 가장 큰 차이점은 Type Identity의 여부이다.

  • Protocol: Type Identity를 가지지 않는다.
  • Opaque Type: Type Identity를 가진다.

이로 인해서 실제 사용시 다음과 같은 차이가 발생한다.

1. Return Value

다음과 같은 코드가 있다.

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 getProtoQuadrilateralgetOpaqueQuadrilateral이 있는데,
두 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.

2. Self 및 associatedType 사용

위에서 Protocol은 Type Identity를 가지지 않고, Opaque Type은 Type Identity를 가진다고 했다.
Type Identity의 여부는 SelfassociatedType의 추론 조건중 하나이다.
즉, Type Identity를 가지지 않게 되면 SelfassociatedType을 추론할 수 없으므로 이를 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 containerProtoContainer 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

profile
iOS, Swift Dev

0개의 댓글