简单来说:some(Opaque types)可以用于协议,但它的用法和限制与“谁来决定具体类型”密切相关。
在 Swift 中,some 的本质是反向泛型。它的核心规则是:具体类型必须在编译期由“实现者”确定,且全局唯一。
1. 什么时候可以使用 some?
A. 作为属性或函数的返回值(最常见)
这是 some 的主战场。你可以声明一个协议,其方法返回 some 类型。
Swift
protocol ShapeFactory {
func makeDefaultShape() -> some Shape
}
struct CircleFactory: ShapeFactory {
func makeDefaultShape() -> some Shape {
return Circle() // 具体的 Circle 类型对调用者隐藏
}
}
B. 作为协议关联类型(Associated Types)的约束
从 Swift 5.7 开始,你可以直接在协议定义中使用 some 来简化关联类型的约束。
Swift
protocol Container {
var content: some Equatable { get } // 这里的 content 实际上隐式定义了一个关联类型
}
C. 作为函数参数 (Swift 5.7+)
这其实是泛型的简写。func test(value: some Shape) 等同于 func test<T: Shape>(value: T)。
2. 什么时候“不能”使用(局限性)?
虽然 some 很强大,但在以下场景中它会失效或不适用:
A. 需要“动态切换”返回类型时
这是 some 的致命弱点。因为 some 要求返回路径必须是唯一的具体类型。
-
错误示范:
Swift
func getShape(isCircle: Bool) -> some Shape { if isCircle { return Circle() } else { return Square() } // ❌ 报错:返回类型不一致 } -
解决办法:如果你需要这种动态性,必须改用
any Shape(Existential type)。
B. 无法在编译期确定具体类型时
如果你的类型是基于运行时数据(比如从服务器下载的插件、动态生成的类)决定的,some 无法工作。编译器必须能“看穿”函数体并找到那个唯一的具体类型。
C. 用于类(Class)的存储属性时
你不能声明一个 var myView: some View 作为类的存储属性(除非它有初始值并且是 final 的,或者在 SwiftUI 的特殊宏环境下)。
- 原因:存储属性需要确定的内存布局。如果类型是不透明的,编译器无法在对象初始化前预留确定的空间。
3. some vs any 的选择决策图
为了帮你决定在协议设计中使用哪种,可以参考这个逻辑:
4. 特殊限制:不透明类型的递归
你不能定义一个无限递归的不透明类型。
-
错误示范:
Swift
func recursiveFunction() -> some Equatable { return recursiveFunction() // ❌ 编译器无法推断出底层的具体类型 }
总结
| 维度 | some Protocol | any Protocol |
|---|---|---|
| 决定权 | 实现者(函数内部)决定类型 | 调用者(赋值时)决定类型 |
| 灵活性 | 低(必须唯一) | 高(可随时更换具体类型) |
| 性能 | 高(静态分发、内联) | 低(动态分发、内存拆装箱) |
| 适用场景 | 库/API 设计、SwiftUI 视图 | 异构数组(如 [any Shape])、动态插件 |