5 min read

Swift 5.7 的 some 和 any 关键词

此篇文章由 AI 辅助生成,本人对内容进行校对格式化,并添加参考文章。

概念定义

在 Swift 5.7 中,anysome 关键词具有不同的用途。any 关键词用于创建存在类型(existential type),它表示一个遵循特定协议的任意类型。在 Swift 5.7 中,创建存在类型时,必须使用 any 关键词,否则会出现编译错误。

与之相反,some 关键词用于创建不透明类型(opaque type),它表示一个遵循特定协议的具体类型,但该类型在编译时是未知的。some 关键词在 Swift 5.1 中引入,对于 SwiftUI 的 View 协议至关重要,因为 View 协议定义了关联类型,不能直接用作类型。简而言之,any 关键词表示一个遵循特定协议的任意类型,而 some 关键词表示一个遵循特定协议的具体但未知类型。这两个关键词都应用于协议,但它们在泛型和协议处理方面有着不同的作用。

理解透明类型 opaque type

在 Swift 中,不透明类型(opaque type)是一种特殊的类型,它允许你隐藏函数或计算属性的具体返回类型。使用 some 关键词声明不透明类型时,编译器可以访问类型信息,但模块的客户端无法访问。这使得我们可以编写灵活、简洁且健壮的 Swift 代码。

不透明类型的优势在于它们保留了类型标识,这意味着函数实现可以选择返回值的类型,而这种类型选择对调用函数的代码是抽象的。与协议类型相比,不透明类型指代一个特定的类型,尽管函数调用者无法看到具体的类型。此外,Swift 可以推断关联类型,这使得在协议类型无法用作返回值的地方可以使用不透明返回值。一个典型的不透明类型应用场景是在 SwiftUI 视图的 body 中。例如:

var body: some View {
  // Your view code
}

在这个例子中,body 的返回类型是一个遵循 View 协议的不透明类型,这意味着我们不需要指定具体的返回类型,同时保留了类型信息。

理解存在类型 existence type

在 Swift 中,"existential type"(存在类型)是一种表示任何特定类型存在的类型,而不指定具体是哪种类型。它源于哲学中的存在和存在概念。在编程上下文中,存在类型通常与协议和泛型相关。

当值类型遵循某个协议时,它们可能会存储在 existential(存在性)容器中。Existential(存在性)容器是一种用于存储运行时未知类型的值的通用容器。例如,当你在 Swift 中使用协议名称(如 Shape)作为类型时,它实际上表示一个存在类型。这意味着,你可以在不知道具体类型的情况下使用这个类型。然而,存在类型可能会导致额外的运行时开销,因为它们需要在运行时处理类型信息。因此,在某些情况下,使用泛型参数而不是存在类型可能更为高效。

总之,Swift 中的 "existential type" 是一种表示特定类型存在但不指定具体类型的类型,通常与协议和泛型一起使用。

Swift 中的存在类型和其他编程语言中的存在类型有何不同?

Swift 的存在类型通常与协议和泛型一起使用,这可能与其他编程语言的实现有所不同。

例如,在 Haskell 中,存在类型通常与类型类(typeclass)系统一起讨论,可以用来存放所有满足某个类型类约束的类型。

而在 Rust 中,dyn Trait 是典型的存在类型,表示满足某个特质(Trait)的类型。尽管 Swift 的存在类型与其他编程语言中的存在类型在概念上相似,但它们在实现和使用方式上可能有所不同。

使用场景

some 关键词的使用场景:在 Swift 中,当我们尝试使用具有 Self 或关联类型要求的协议时,会遇到编译器错误。例如,当我们尝试使用 Collection 协议时,会遇到这个问题。为了解决这个问题,我们可以使用 some 关键词,它表示一个遵循特定协议的具体但未知类型。这使得我们可以在不关心关联类型的情况下使用协议。

func doSomething(with collection: some Collection) {
  // Do something
}

any 关键词的使用场景:当我们需要处理多个符合相同协议的具体类型时,可以使用 any 关键词。例如,假设我们有一个 ContentItem 协议,我们希望创建一个通用类型的 ArticleSelectionController,可以用来选择任何符合 ContentItem 的值。在这种情况下,我们可以使用 any 关键词来表示一个遵循特定协议的任意类型。

class ArticleSelectionController {
  var items: [any ContentItem]

  init(items: [any ContentItem]) {
    self.items = items
  }
}

需要注意的是,尽管 any 关键词允许我们处理多个具体类型,但它会引入类型擦除,因此在可能的情况下,优先使用静态类型(如 some 关键词)。

参考