421
社区成员
不透明类型
是指我们被告知对象的功能
而不知道对象具体是什么类型
。
- 具有不透明返回类型的
函数
或方法
会隐藏返回值的类型信息。函数不再提供具体的类型作为返回类型,而是根据它支持的协议
来描述返回值。- 在处理模块和调用代码之间的关系时,隐藏类型信息非常有用,因为返回的底层数据类型仍然可以保持私有。而且不同于返回
协议
类型,不透明类型能保证类型一致性
——编译器
能获取到类型信息,同时模块使用者
却不能获取到。
协议类型
作为返回值时,返回对象仅能调用协议中定义的属性
和方法
。与返回值类型是否是不透明返回值类型没有关系
protocol MyProtocol {
///属性
var nameOfProtocol:String {get set}
///方法
func doSomeTingOfProtocol () -> Void
}
struct MyStruct:MyProtocol {
///协议相关
var nameOfProtocol: String = "协议属性"
func doSomeTingOfProtocol() {
print("学习")
}
///自定义相关
var nameOfSelf:String = "自定义属性"
func doSomeTingOfSelf() {
print("打游戏")
}
}
func testMethods() -> some MyProtocol { ///返回遵守MyProtocol协议的类型
return MyStruct()
}
let object = testMethods()
调用属性:
不透明类型可以被认为是“实现某个协议的具体类型”
。 它的语法: some Protocol
,
举个栗子:
func makeA() -> some Equatable { ///返回类型是 some + Equatable协议 的不透明类型
"A" ///单表达式函数的隐式返回,此处隐藏了return
}
尽管具体类型永远不会暴露给函数的调用者,但返回值仍保持强类型。 这样做的原因是,编译器知道具体的类型:
let a = makeA()
let anotherA = makeA()
print(a == anotherA) // ✅ The compiler knows that both values are strings
下面让我们测试遵循相同协议
的不同类型
的不透明类型是否相等:
func makeOne() -> some Equatable { ///返回类型是 some + Equatable协议 的不透明类型
1 ///单表达式函数的隐式返回,此处隐藏了return
}
let one = makeOne()
print(a == one) // ❌ Compilation error: `a` and `one` are of different types, although both conform to `Equatable`
编译器会认为两个不透明类型不相等:
var x: Int = 0
x = makeOne() // ❌ Compilation error: Cannot assign value of type 'some Equatable' to type 'Int'
该函数每次必须返回相同的不透明类型
:
func makeOneOrA(_ isOne: Bool) -> some Equatable {
isOne ? 1 : "A" // ❌ Compilation error: Cannot convert return expression of type 'Int' to return type 'some Equatable'
}
这能让调用者依靠不透明类型在运行时保持类型一致性
。
从编译器的角度来看,不透明类型与其基础类型
等效
。 编译器将其抽象化
,仅将类型公开为符合给定约束集
的某种形式。
不透明类型
是一种特殊的泛型
。
泛型
是用于类型级别抽象
的Swift语言特性。 它允许将一种类型与满足给定约束集
的任何其他类型以相同的方式
使用。
调用者
约束func foo<T: Equatable>() -> T { ... }
let x: Int = foo() // T == Int, chosen by caller
let y: String = foo() // T == String, chosen by caller
被调用者
约束func bar() -> some Equatable { ... }
let z = bar() // z is abstracted to Equatable. Concrete type is chosen by bar() implementation
不透明类型有时称为
“反向泛型”
不透明类型看起来
像协议
,行为也很像协议。 因此,重要的是要证明它们之间的差异
。
Self
或associatedtype
要求的协议。 相反,不透明类型却可以:// Equatable protocol declaration from the Swift standard library
public protocol Equatable {
static func == (lhs: Self, rhs: Self) -> Bool
}
func makeTwo() -> Equatable { 2 } // ❌ Protocol 'Equatable' can only be used as a generic constraint because it has Self or associated type requirements
func makeThree() -> some Equatable { 3 } // ✅
SwiftUI
中你会发现大量使用some
和associatedtype
修饰的语法。所以了解一下还是很有必要的。
不同的协议类型
。 相反,它每次必须返回相同的不透明类型
:///自定义协议
protocol P {}
///拓展基础类型
extension Int: P {}
extension String: P {}
///区别
func makeIntOrString(_ isInt: Bool) -> P { isInt ? 1 : "1" } // ✅
func makeIntOrStringOpaque(_ isInt: Bool) -> some P { isInt ? 1 : "1" } // ❌ Compilation error
还是体现了不透明类型的特点:
类型一致性
先来看一段代码,它展现了原来 protocol 能力上的缺陷:
///协议:Shape
protocol Shape {}
/// Rectangle
struct Rectangle: Shape {}
/// Union
struct Union<A: Shape, B: Shape>: Shape {
var a: Shape
var b: Shape
}
/// Transformed
struct Transformed<S: Shape>: Shape {
var shape: S
}
///协议:GameObject
protocol GameObject {
associatedtype ShapeType: Shape
var shape: ShapeType { get }
}
/// EightPointedStar
struct EightPointedStar: GameObject {
var shape: Union<Rectangle, Transformed<Rectangle>> {
return Union(a:Rectangle(), b:Transformed(shape: Rectangle()))
}
}
缺陷有两方面:
- 上述代码是可以编译通过的,但是 EightPointedStar 的 shape 返回类型又臭又长,被暴露了出去;如果换成 Shape 则编译不通过,原因是
associatedtype
ShapeType 要求必须指定具体的类型
,而 Shape 不实现 Shape 本身。- 假如 Shape 协议中含有
Self
或者associatedtype
,无法作为函数的返回参数。这是 Swift 泛型系统长久以来的一个问题。
不透明型解决了上述问题,它为 protocol 作为返回类型提供以下能力:
- 语法上
隐藏具体类型
,所以叫做不透明结果类型- 强类型:类型参数不丢失
- 允许带有
Self
或者associatedtype
的protocol
作为返回类型
在 Swift 5.1 中,将返回类型改成 some + protocol
的形式:
struct EightPointedStar: GameObject {
var shape: some Shape {
return Union(a:Rectangle(), b:Transformed(shape: Rectangle()))
}
}
这类的泛型特性也被称作“反向泛型”
,因为具体的类型
参数是由“实现部分”指定并隐藏起来的,而一般的泛型
是由“调用者”所指定的。
上面这个例子中:语法上隐藏具体类型很明显,再举一个例子说明其它 2 个特性:
func foo<T: Equatable>(x: T, y: T) -> some Equatable {
let condition = x == y
return condition ? 42 : 11
}
let x = foo(x: "apples", y: "bananas")
let y = foo(x: "apples", y: "oranges")
print(x == y) // 这里可以被调用是因为泛型系统保留了强类型
这个例子显示了不透明结果类型的三个特性:
既对外隐藏了具体的 Equatable 类型;又保留了强类型(使得 x == y)可以比较;还支持了 Equatable 这个带 Self 的泛型约束。
不透明结果类型对于函数实现有一个增强的要求:
函数实现必须返回同一个
具体类型
,以上述代码为例:不能返回 Equatable 或者是 不同类型的 Equatable 的实现。
这里还有一个小问题:
既然 x 和 y 可以直接比较,那么它们可否直接赋值给 var i: Int 呢?答案是对于静态类型系统是不可以的,它保留了 some Equatable 的具体类型隐藏功能,但是如果使用动态类型判断 as? Int,则可以转换成 Int。
当设计通用代码时,some
关键字特别有用,例如库或特定领域的语言。 底层类型永远不会暴露给使用者,尽管他们可以利用其静态特性。 由于它们是在不透明类型上解析的,因此可以利用具有关联类型(associated types
)和Self
要求的协议。
不透明类型可以把库的使用者和库的内部实现分隔开。
以下是有关Swift不透明类型和some关键字的总结:
- 不透明类型可以被认为是具有私有
基础类型
的协议。- 不透明类型是由
函数实现者
定义的,而不是由调用者定义
的。- 一个函数每次必须返回
相同的
不透明类型。- 允许带有
Self
或者associatedtype
的protocol
作为返回类型