不透明类型和Swift中的some关键字

4,721 阅读3分钟

不透明类型是Swift类型系统的一个特性。 它可以指定实现特定协议的未命名但具体的类型。

在本文中,我们来研究:

  • 什么是不透明类型?
  • 不透明类型,泛型和协议之间有什么区别?
  • 什么时候使用不透明类型?

总览

不透明类型可以被认为是“实现某个协议的具体类型”。 它的语法:some Protocol,举个栗子:

func makeA() -> some Equatable { "A" }

尽管具体类型永远不会暴露给函数的调用者,但返回值仍保持强类型。 这样做的原因是,编译器知道具体的类型:

let a = makeA()
let anotherA = makeA()

print(a == anotherA) // ✅ The compiler knows that both values are strings

下面让我们测试遵循相同协议的不同类型的不透明类型是否相等:

func makeOne() -> some Equatable { 1 }

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

不透明类型有时称为“反向泛型”

不透明类型和协议

不透明类型看起来像协议,行为也很像协议。 因此,重要的是要证明它们之间的差异。

  1. 不能从函数返回带有Selfassociatedtype要求的协议。 相反,不透明类型却可以:
// 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中你会发现大量使用someassociatedtype修饰的语法。所以了解一下还是很有必要的。

  1. 一个函数可以返回不同的协议类型。 相反,它每次必须返回相同的不透明类型:
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

何时使用some关键字

当设计通用代码时,some关键字特别有用,例如库或特定领域的语言。 底层类型永远不会暴露给使用者,尽管他们可以利用其静态特性。 由于它们是在不透明类型上解析的,因此可以利用具有关联类型(associated types)和Self要求的协议。

不透明类型可以把库的使用者和库的内部实现分隔开。

总结

以下是有关Swift不透明类型和some关键字的总结:

  • 不透明类型可以被认为是具有私有基础类型的协议。
  • 不透明类型是由函数实现者定义的,而不是由调用者定义的。
  • 一个函数每次必须返回相同的不透明类型。
  • 允许带有Self或者 associatedtypeprotocol 作为返回类型