Opaque Type은 함수가 호출하는 코드에 구체적인 타입을 드러내지 않고 어떤 타입의 값을 반환할 수 있도록 하는 Swift의 메커니즘입니다. 반환 타입에서 some 키워드가 가장 유명한 예입니다: SwiftUI에서 some View는 “함수가 View를 따르는 어떤 타입을 반환하지만, 그것이 뭐인지는 구현 세부 사항이다”라는 뜻입니다. Opaque type은 타입의 정체성을 보존합니다(타입으로서의 프로토콜과 달리). 이를 통해 컴파일러가 코드를 최적화하고 반환된 타입의 일관성을 보장할 수 있습니다. Swift Book, 2025에 따르면, opaque types는 연관 타입을 가진 프로토콜의 문제를 해결하여, 함수에서 해당 프로토콜의 값을 반환할 수 있도록 합니다.
중요 포인트
Opaque Type은 some 키워드로 선언되는 반환 타입으로, 호출하는 코드로부터 구체적인 구현을 숨합니다. 호출자는 반환된 값이 특정 프로토콜을 따른다는 것만 알고, some 뒤에 어떤 타입이 있는지는 알 수 없습니다. 한편, 컴파일러는 정확한 타입을 알고 있으며, 정적 디스패치와 최적화에 사용합니다.
Swift 5.1 (SE-0244)에서 opaque types가 도입되기 전에는, 박싱 래퍼 없이 함수에서 연관 타입을 가진 프로토콜을 반환하는 것이 불가능했습니다. 예를 들어, Equatable 프로토콜에는 연관 타입이 있으며, 함수가 단순히 Equatable을 반환할 수 없었습니다 — 컴파일러가 “프로토콜은 제너릭 제약으로만 사용할 수 있습니다”는 오류를 내벃습니다. Opaque type이 이 문제를 해결했습니다.
func makeInt() -> some Equatable {
return 42
}
func makeString() -> some Equatable {
return "Hello"
}
// 컴파일러는 makeInt가 Int를 반환함을 알고 있다
// makeInt() == makeString() — ❌ 오류, 다른 타입
두 함수 모두 some Equatable을 반환하지만, 구체적인 타입은 다릅니다: Int와 String입니다. 이것들을 ==로 비교하려면 컴파일 오류가 발생합니다. 이는 opaque type이 특정 호출이 동일한 타입을 반환함을 보장하지만, 서로 다른 함수 간에는 보장하지 않기 때문입니다. 이것은 기능이지 버그가 아닙니다: opaque type은 타입으로서의 프로토콜(any Equatable)이 잃는 타입의 정체성을 보존합니다.
Generic과 Opaque Type은 동전의 양면입니다. 제네릭은 호출하는 코드가 타입을 선택할 수 있게 하며, opaque type은 함수가 호출하는 코드로부터 타입을 숨할 수 있게 합니다. 차이점은 제어의 방향에 있습니다.
| 특성 | Generic | Opaque some |
|---|---|---|
| 누가 타입을 선택하나 | 호출하는 코드 | 함수/메소드 |
| 타입 정체성 | 보존됨(안정) | 보존됨(안정) |
| 반환 브랜치 수 | 한 가지(제네릭 통해) | 모든 브랜치에서 동일한 타입 |
| 사용추 | 알고리즘, 데이터 구조 | SwiftUI, 팩토리 메소드 |
제네릭 함수에서는 코리가 사용할 타입을 결정합니다. 함수는 제약을 만족시키는 모든 T에서 동작해야 합니다. Opaque type에서는 코리가 구체적인 타입을 알 수 없으며, 구현이 결정을 낵니다.
// Generic: 코리가 타입을 선택함
func identity<T>(_ value: T) -> T { value }
let x: Int = identity(42)
// Opaque: 함수가 타입을 숨김
func makeSomeEquatable() -> some Equatable { 42 }
let y = makeSomeEquatable()
제네리그와 opaque type 사이의 선택은 의도에 따라 다릅니다. 호출하는 코드가 타입을 선택해야 한다면 제네릭을 사용하세요. 함수가 구현 세부 사항을 숨겨야 한다면 some을 사용하세요. SwiftUI는 body가 내부적으로는 유연하지만 외부적으로는 안정적이어야 하기 때문에 some View를 선택했습니다.
some은 Swift 5.1 (SE-0244)에서 도입된 Swift 키워드입니다. 반환 위치에서 opaque type을 선언하는 데 사용되며, 파라미터(SE-0341)와 특성에서도 사용됩니다. some은 구체적인 타입이 안정적이고 컴파일러에게 알려져 있지만 외부 코드에서는 숨겠다는 것을 보장합니다.
Swift 5.7부터, some은 반환 위치뿐만 아니라 파라미터에서도 사용할 수 있습니다. 파라미터에서 some Equatable은 “이 함수는 모든 Equatable 타입을 수락하지만, 특정 보디 내의 모든 호출은 동일한 타입을 본다”는 뜻입니다.
func areEqual(_ a: some Equatable, _ b: some Equatable) -> Bool {
// a와 b — 잠재적으로 다른 타입, ==가 직접 동작하지 않음
return isEqual(a, b)
}
func isEqual<T: Equatable>(_ a: T, _ b: T) -> Bool {
return a == b
}
파라미터에서 some을 사용하면
any는 Swift 5.6+의 키워드로, 실존적 타입(타입으로서의 프로토콜)을 명시적으로 선언하는 데 사용됩니다. some과 달리, any는 타입의 정체성을 지우며, 컴파일러는 프로토콜 뒤에 어떤 구체적인 타입이 있는지 알 수 없습니다. 이것은 유연성(동일한 배열에 서로 다른 타입을 저장할 수 있음)을 제공하지만, 성능의 희생이 있습니다.
some — 정적 다형성: 컴파일러가 구체적인 타입을 알고, 직접 디스패치를 사용하며, 코드를 인라인할 수 있습니다. any — 동적 다형성: 가상 메소드 테이블(실존적 컨테이너)이 사용되며, 간접성이 추가됩니다.
protocol Drawable {
func draw()
}
// some: 정적 타입이 알려져 있음
func makeDrawable() -> some Drawable {
return Circle() // 단일 반환 타입
}
// any: 동적, 다른 타입을 저장할 수 있음
var shapes: [any Drawable] = [Circle(), Square()]
shapes.append(Triangle())
some과 any 사이의 선택은 성능과 유연성 간의 타협입니다. Some이 더 빠르지만 단일 구현로 제한됩니다. Any가 더 유연하지만(타입을 혼합할 수 있음) 동적 디스패치로 인해 더 늦습니다. SwiftUI에서 body는 항상 some View를 사용합니다. 그 이유는 각 View의 body가 하나의 구체적인 타입이기 때문입니다.
Opaque Type은 Swift의 근본적인 문제를 해결합니다: 연관 타입을 가진 프로토콜(PAT)은 타입으로 직접 사용할 수 없습니다. 함수는 단순히 Collection을 반환할 수 없으며, 컴파일러가 Element를 지정하도록 요구합니다. some Collection은 연관 타입을 숨겨서 이 문제를 해결합니다.
Opaque type 없이 Collection을 반환하려면 구체적인 타입(Array
func makeReversedCollection<T>(
of array: [T]
) -> some Collection {
return array.reversed()
}
let result = makeReversedCollection(of: [1, 2, 3])
// result — ReversedCollection>, hidden from caller
for item in result {
print(item)
}
result는 반복할 수 있지만, ReversedCollection의 특성에 직접 액세스할 수 없습니다. 이것은 캡슐화를 보호합니다: 나중에 reversed()를 다른 구현의 다른 메소드로 대체하더라도 호출하는 코드가 깨지지 않습니다. Opaque type은 API를 바꾸지 않고 구현을 변경할 수 있는 자유를 제공합니다.
some View는 opaque type의 가장 유명한 사용입니다. SwiftUI에서 각 View는 body를 some View로 선언합니다. 이는 body가 어떤 구체적인 View 타입을 반환한다는 의미이지만, 개발자는 그것이 토플View, Group, ModifiedContent 또는 프레임워크의 다른 타입인지 고민할 필요가 없습니다.
Opaque type 없이는 body가 구체적인 타입을 반환해야 했으며, 예를 들어 ModifiedContent<Button<Text>, Padding>이라는 것은 비실용적입니다. some View는 이 복잡성을 숨합니다. 컴파일러는 컴파일 시점에 body의 정확한 타입을 자동으로 추론합니다.
struct ContentView: View {
var body: some View {
VStack {
Text("안녕하세요")
.font(.title)
Button("탚하세요") {
print("탚되었습니다")
}
}
.padding()
}
}
컴파일러는 body를 ModifiedContent<VStack<TupleView<(Text, Button<Text>)>>, Padding>으로 추론합니다. 개발자는 some View를 봅니다. 레이아웃을 VStack에서 HStack으로 변경하면, 컴파일러가 자동으로 타입을 재추론합니다 — 수동 편집이 필요 없습니다. 이것이 opaque type의 매력입니다: 개발자는 인터페이스 로직에 집중하고, 구성 타입에 집중하지 않습니다.
자주 묻는 질문
Opaque Type은 some 키워드로 선언되는 타입으로, 호출하는 코드로부터 구체적인 구현을 숨합니다. 컴파일러는 정확한 타입을 알고 있지만, 함수를 사용하는 개발자는 프로토콜만 봉니다.
some은 정적 정체성을 가진 opaque type입니다: 컴파일러가 구체적인 타입을 앍니다. any는 동적 디스패치를 가진 실존적 타입으로, 타입 정체성이 지워집니다. Some이 더 효율적이고, any가 더 유연합니다.
some View는 컴파일러가 자동으로 추론하는 body의 복잡한 구체적인 타입을 숨합니다. 이는 개발자가 제네릭 래퍼(VStack, Group, ModifiedContent)로 구성된 정확한 타입을 작성하는 것에서 해방시킵니다.
네, Swift 5.7부터 가능합니다. 파라미터에서 some은 제네릭 파라미터의 문법적 서괜입니다. 함수 선언을 간단하게 하며, 특힐 각 some-파라미터가 별도의
컴파일러가 오류를 내밽니다: opaque type은 모든 반환 브랜치가 동일한 구체적인 타입을 반환할 것을 요구합니다. 이것은 타입 정체성을 보존하기 위한 의도적인 설계입니다. 다른 타입을 반환해야 한다면 any를 사용하세요.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.