문제의 역사:
Opaque types (some)는 Swift 5.1에서 도입되어 함수나 속성의 반환 타입을 추상화하는 새로운 방법을 제공하는데, 이때 타입은 컴파일러에 의해 알려지지만 사용자에게는 숨겨집니다. 이는 protocol existentials (any Protocol)의 대안이지만, 함수 내부에서 특정 타입에 강하게 바인딩됩니다.
문제점: 함수가 associatedtype이 있는 프로토콜을 반환할 때 (예: Sequence), 직접적으로 다음과 같이 작성할 수 없습니다:
func makeNumberSequence() -> Sequence { ... } // 오류
Protocol existentials는 어떤 구현도 반환할 수 있지만, 매 호출시 항상 동일한 타입을 보장하지 않습니다:
func foo() -> any View { ... }
이로 인해 예측 불가능성과 낮은 타입 안전성이 발생합니다.
해결책: Opaque result type을 사용합니다:
func makeNumbers() -> some Sequence { [1, 2, 3] }
이제 컴파일러는 실제 반환 타입을 정확히 알고 있지만, 외부에는 숨겨져 있습니다. 이는 성능 최적화, 안전성을 제공하고, SwiftUI DSL을 사용할 수 있게 하며, 모듈 간의 타입 교환을 용이하게 합니다.
주요 특징:
opaque types는 값을 저장하는 데 (예: 클래스 속성) 사용할 수 있나요?
아니요. Opaque types는 함수나 computed properties의 반환 값에만 적용됩니다. 값을 저장하거나 값의 배열을 위한 경우 existentials (any Protocol)를 사용합니다.
하나의 함수의 다른 분기가 some으로 서로 다른 타입을 반환할 수 있나요?
아니요. 컴파일러는 두 (또는 모든) 분기가 동일한 특정 타입을 반환할 것을 요구합니다. 그렇지 않으면 오류가 발생합니다:
func foo(flag: Bool) -> some Sequence { if flag { return [1, 2, 3] } else { return ["a", "b", "c"] // 오류 } }
여러 함수 사이에서 반환 타입을 식별하는 데 some을 사용할 수 있나요?
아니요. 각 some을 사용하는 함수는 고유한 숨겨진 타입을 반환하며, 비록 실제로는 동일한 배열이더라도 서로 다른 프로토콜이나 서로 다른 숨겨진 타입을 사용할 경우, 한 함수의 결과를 다른 함수의 매개변수로 사용할 수 없습니다.
프로젝트에서 모든 것이 any Protocol을 통해 반환되어, 컬렉션의 타입이 손실되고, downcast 시 버그가 발생하며, compile-time 최적화가 느려집니다.
장점:
단점:
SwiftUI 디자인에서 컴포넌트는 some View를 반환하고, 각 모듈은 내부 타입을 명확히 정의합니다. 번들 크기가 줄어들고, 빌드 시간이 단축됩니다.
장점:
단점: