一、Opeque 是什么
- Protocol 前面加上
some
就建立了 Opaque 类型。 - 主要用在函数的返回类型上。
- 从 Swift 5.7 开始,
some
的用途还可以用于:- 方法参数
func process(_ value: some Equatable)
- 存储属性
var content: some Equatable
- 局部变量
let item: some Equatable = 42
- 下标
subscript(index: Int) -> some View
- 方法参数
- 从 Swift 5.7 开始,
- 把 Protocol 当类型用,并且确保只有一个具体类型,所以不用担心 Self 和 associatedtype 的问题。
下面有两个方法,展示了泛型和 Opeque。
func getLargerGeneric<T: Comparable>(_ a: T, _ b: T) -> T {
a > b ? a : b
}
// 使用 some 关键字返回不透明类型
func getLargerOpaque<T: Comparable>(_ a: T, _ b: T) -> some Comparable {
a > b ? a : b
}
// 类型:Int
let genericResult = getLargerGeneric(1, 5)
// 类型:some Comparable
let opaqueResult = getLargerOpaque(1, 5)
- 在 protocol 前面加上
some
,就建立了一个 Opaque 不透明类型。 - Opaque 的类型一定是某“一个”类型。
- 调用方拿到返回值,也只知道它是某一个固定的 Comparable 类型,无法知道具体的类型。
二、为什么需要 Opaque
隐藏复杂类型(以 Swift 为例):
// 不使用 Opaque struct SwiftUIView: View { var body: Text { Text("Hello World") } } struct SwiftUIView: View { var body: VStack<TupleView<(Text, Image)>> { VStack { Text("Hello World") Image("person") } } }
随着 UI 组件的增加,
body
的返回类型会变得复杂,但对调用方来说只需知道它符合View
协议。使用 Opaque 可以简化:// 使用 Opaque struct SwiftUIView: View { var body: some View { VStack { Text("Hello World") Image("person") } } }
性能优化:
some
在编译时确定具体类型,避免了any
的动态派发开销。- 适合高频调用的函数或需要类型确定的场景(如 SwiftUI 的 body)
类型安全:
通过
some
强制返回值的类型一致性:// ✅ 编译通过:两个分支返回相同类型(Int) func randomNumber() -> some Equatable { Bool.random() ? 42 : 100 } // ❌ 编译错误:分支返回不同类型(Int 和 String) func randomValue() -> some Equatable { Bool.random() ? 42 : "42" }
封装灵活性:
- 当需要更换返回类型时,不影响外部逻辑(外部只能操作协议定义的部分)
三、场景示例
1. 省略复杂类型
下面展示了对一个范围数字进行的反转和切片操作,每一步操作返回的类型都会在上一层的类型上进行一层包装,导致返回类型变得复杂。使用 Opaque 可以隐藏这些无关类型:
// 类型:ClosedRange<Int>
let range = 1...1000
// 类型:ReversedCollection<ClosedRange<Int>>
let reverse = range.reversed()
// 类型:Slice<ReversedCollection<ClosedRange<Int>>>
let pre = reverse.prefix(5)
// 不使用 Opaque,返回类型很复杂,并且这个实际类型对调用方来说没有任何意义
func suffix<T: Sequence>(_ sequence: T) -> Slice<ReversedCollection<T>> {
sequence.reversed().prefix(5)
}
// 使用 Opaque,省略无关的类型,只需知道是一个 Sequence 即可
func suffix<T: Sequence>(_ sequence: T) -> some Sequence {
sequence.reversed().prefix(5)
}
// 类型:some Sequence
let nums = suffix(1...1000)
// 可以对 nums 调用 Sequence 的操作
2. 结合 Optional
Swift 5.7+ 开始支持
// 返回 (some Equatable)?
func maybeNumber() -> (some Equatable)? { 42 }
四、注意事项
类型一致性
即使两个函数返回相同的逻辑类型,编译器也会视为不同:
func a() -> some Equatable { 1 } func b() -> some Equatable { 1 } // ❌ 无法比较:a() 和 b() 的返回值被视为不同的不透明类型 a() == b()
与泛型的区别
- 泛型(如
func foo<T>() -> T
)由调用方决定类型。 some
由函数内部决定类型。
- 泛型(如
协议关联类型:
some
可以简化关联类型的处理:protocol Processor { associatedtype Output func process() -> Output } // 使用 some 隐藏关联类型 func createProcessor() -> some Processor { struct IntProcessor: Processor { typealias Output = Int func process() -> Int { 42 } } return IntProcessor() }