简单来说: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])、动态插件 |
英文版
7-19. [Advanced] Can Opaque Types (some) be used with Protocols? When should they be avoided?
In simple terms: some (Opaque types) can be used with protocols, but its usage and restrictions are strictly governed by "who decides the concrete type."
In Swift, the essence of some is Reverse Generics. Its core rule is: The concrete type must be determined by the "implementer" at compile-time and must be globally unique within that scope.
1. When can you use some?
A. As a Property or Function Return Type (Most Common)
This is the primary domain of some. You can define a protocol with methods that return an opaque type.
Swift
protocol ShapeFactory {
func makeDefaultShape() -> some Shape
}
struct CircleFactory: ShapeFactory {
func makeDefaultShape() -> some Shape {
return Circle() // The concrete 'Circle' type is hidden from the caller
}
}
B. As a Constraint for Associated Types
Starting from Swift 5.7, you can use some directly in protocol definitions to simplify the constraints on associated types.
Swift
protocol Container {
var content: some Equatable { get } // Implicitly defines an associated type here
}
C. As a Function Parameter (Swift 5.7+)
This is essentially syntactic sugar for generics. func test(value: some Shape) is equivalent to func test<T: Shape>(value: T).
2. When can you "NOT" use it? (Limitations)
While some is powerful, it fails or is inapplicable in the following scenarios:
A. When "Dynamic Switching" of Return Types is Required
This is the fatal weakness of some. Because some requires the return path to result in a unique concrete type.
-
Incorrect Example:
Swift
func getShape(isCircle: Bool) -> some Shape { if isCircle { return Circle() } else { return Square() } // ❌ Error: Function declares an opaque return type, but has incompatible return statements } -
Solution: If you need this dynamic behavior, you must use
any Shape(Existential type).
B. When the Concrete Type Cannot Be Determined at Compile-time
If your type is decided based on runtime data (e.g., a plugin downloaded from a server or a dynamically generated class), some cannot work. The compiler must be able to "see through" the function body to identify that single concrete type.
C. For Stored Properties in a Class
You cannot declare var myView: some View as a stored property of a class (unless it has an initial value and is final, or within specific SwiftUI macro environments).
- Reason: Stored properties require a fixed memory layout. If the type is opaque, the compiler cannot reserve the exact space required before the object is initialized.
3. Decision Matrix: some vs. any
To help you decide which to use in your protocol design, follow this logic:
4. Special Restriction: Opaque Type Recursion
You cannot define an infinitely recursive opaque type.
-
Incorrect Example:
Swift
func recursiveFunction() -> some Equatable { return recursiveFunction() // ❌ Error: The compiler cannot infer the underlying concrete type }
Summary
| Dimension | some Protocol (Opaque) | any Protocol (Existential) |
|---|---|---|
| Decision Power | Implementer (inside the function) decides | Caller (at assignment) decides |
| Flexibility | Low (must be unique) | High (can swap concrete types at any time) |
| Performance | High (Static dispatch, inlining) | Low (Dynamic dispatch, boxing/unboxing) |
| Use Case | Library/API design, SwiftUI Views | Heterogeneous arrays ([any Shape]), dynamic plugins |