Notice
Recent Posts
Recent Comments
Link
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
Archives
Today
Total
관리 메뉴

sanichdaniel의 iOS 개발 블로그

Opaque Types 본문

swift

Opaque Types

sanich8355 2020. 9. 29. 20:06

요약

Opaque 타입은 변수, 메서드의 리턴타입을 프로토콜 타입으로 숨긴다.
이는 모듈과 모듈을 호출하는 코드 사이에서, 구체 타입을 숨길때 유용하다.
프로토콜을 리턴하는것과의 차이점은 Opaque 타입은 항상 하나의 구체타입을 가르키기에 type의 identity를 간직한다.
물론 caller에게는 프로토콜로만 보인다.
구체 타입은 변수, 메서드의 구현이 결정한다. 이는 caller가 타입을 결정하는 제너릭과 반대이다.
따라서 프로토콜을 리턴하는 상황에서 Self, associatedType 제약이 걸려있는 경우에도,
Opaque 타입은 type identity가 간직되기에 프로토콜을 리턴 할 수 있다.
(프로토콜을 리턴하는 경우에는 type-erasure wrapper클래스를 구현안해도될수도)

Opaque Types

function이나 method중 opaque리턴 타입은 리턴값을 프로토콜 타입으로 반환하여 구체적인 리턴 타입을 숨긴다. 

구체적인 타입 정보를 숨기는건, 모듈과 모듈을 호출하는 사이의 코드의 경계에서 유용하다.

왜냐하면 리턴타입의 구체적인 값은 prviate로 유지될수 있기 때문이다.

 

The Problem that opaque type solves

 

Returning an Opaque Type

generic은 caller가 리턴 타입을 결정한다.

opaque type은 함수의 구현이 리턴하는 타입을 정한다. caller에게 추상화 되있다.

makeTrapezoid()가 리턴하는 구체 타입은 caller에게 프로토콜로 추상화 되어있다.

struct Square: Shape {
    var size: Int
    func draw() -> String {
        let line = String(repeating: "*", count: size)
        let result = Array<String>(repeating: line, count: size)
        return result.joined(separator: "\n")
    }
}

func makeTrapezoid() -> some Shape {
    let top = Triangle(size: 2)
    let middle = Square(size: 2)
    let bottom = FlippedShape(shape: top)
    let trapezoid = JoinedShape(
        top: top,
        bottom: JoinedShape(top: middle, bottom: bottom)
    )
    return trapezoid
}
let trapezoid = makeTrapezoid()
print(trapezoid.draw())

 

opaque type이 여러곳에서 리턴한다면 모든 리턴 값은 같은 타입을 리턴해야한다.

// 잘못됨
func invalidFlip<T: Shape>(_ shape: T) -> some Shape {
    if shape is Square {
        return shape // Error: return types don't match
    }
    return FlippedShape(shape: shape) // Error: return types don't match
}

// 아래 함수는 항상 [T] 타입을 리턴하니 가능
func `repeat`<T: Shape>(shape: T, count: Int) -> some Collection {
    return Array<T>(repeating: shape, count: count)
}

Opaque Type을 리턴하는것과 프로토콜 타입을 리턴하는것의 차이점

타입의 identity를 간직하는지 차이가 있다.

opaque type은 caller에게는 감춰져있지만 하나의 구체 타입을 리턴한다

프로토콜 타입을 리턴하는 경우, 프로토콜을 채택한 모든 타입을 리턴가능하다

func protoFlip<T: Shape>(_ shape: T) -> Shape {
    if shape is Square {
        return shape
    }

    return FlippedShape(shape: shape)
}

프로토콜을 리턴하는 경우 유연하다는 장점이 있지만, 단점으로는 리턴값에 대하여 여러 operation이 불가능하다는 점이 있다.

protoFlip의 리턴값을 가지고는 == 연산자를 사용할수 없다.

Shape에 Equatable 프로토콜을 채택하더라도, Equatable 프로토콜은 Self 제약이 있기에

 

또한 위 protoFlip은 nest가 안된다는 문제가 있다.

// protoFlip의 리턴타입은 프로토콜
// 프로토콜은 프로토콜을 따르지 않는다!!
// 에러!
protoFlip(protoFlip(Square(size: 3)))

위 함수는 프로토콜을 리턴한다. 하지만 인자로 프로토콜을 채택한 타입을 받는다.

프로토콜은 프로토콜을 따르지 않는다. 

이때도 opaque 타입을 이용하면 문제를 해결할수 있다

 

하지만 opaque type은 type의 identity를 간직하기에 Swift는 associted type, Self 요구사항을 추론할수 있다.

그리고 구체 타입이 리턴되기에 == 연산자를 적용할수 있다

덕분에 protocol 타입이 리턴타입이 될수 없는 경우에 opaque 타입을 리턴하는 방식을 사용할수 있다.

protocol Container {
    associatedtype Item
    var count: Int { get }
    subscript(i: Int) -> Item { get }
}
extension Array: Container { }

func makeOpaqueContainer<T>(item: T) -> some Container {
    return [item]
}
let opaqueContainer = makeOpaqueContainer(item: 12)
let twelve = opaqueContainer[0]
print(type(of: twelve))
// Prints "Int"

 

사용되는곳

라이브러리 프레임워크 코드를 작성하거나, flexible한 코드를 작성하는 경우

프로토콜을 리턴하는데 Self, associated 요구사항을 가진 경우

 

출처

www.donnywals.com/understanding-opaque-return-types-in-swift-5-1/

'swift' 카테고리의 다른 글

KeyPath  (0) 2020.10.03
Generic where cause  (0) 2020.10.01
enum vs protocol as UseCase  (0) 2020.09.28
Type Erasure  (0) 2020.09.28
initializer  (0) 2020.09.26