Swift中的类型占位符

108 阅读4分钟

Swift中的类型占位符

hudson译 原文

Swift的类型推断功能从一开始就是该语言的核心部分,大大减少了在声明具有默认值的变量和属性时手动指定类型的需求。例如,表达式var number = 7 不需要包含任何类型注释,因为编译器能够推断出值7是Int。

Swift 5.6作为Xcode 13.3的一部分发布,通过引入“类型占位符”的概念,继续扩展这些类型推断能力,该概念在处理集合和其他范型类型时非常方便。

例如,假设想用默认整数值创建一个 Combine 的 CurrentValueSubject 实例。初步想法可能是简单地将默认值传递给该主题的初始化器,然后将结果存储在本地let中(就像创建普通Int值时一样)。然而,这样做会带来以下编译器错误:

// Error: “Generic parameter ‘Failure’ could not be inferred”
let counterSubject = CurrentValueSubject(0)

这是因为CurrentValueSubject是一个范型类型,要使其具体化,不仅需要专门的Output类型,还需要专门Failure类型——即主题(Subject)能够抛出的错误类型。

由于不希望主题在这种情况下抛出任何错误,k可设置失败类型为Never(这是使用Combine和Swift并发时的常见惯例)。但为了做到这一点,在Swift 5.6之前,也需要明确指定Int输出类型——像这样:

let counterSubject = CurrentValueSubject<Int, Never>(0)

然而,从Swift 5.6开始,情况不再如此——因为现在可以为主题的输出类型使用类型占位符,并再次利用编译器自动推断该类型,就像声明一个普通的Int值一样:

let counterSubject = CurrentValueSubject<_, Never>(0)

这很好,但并不是世界上最大的进步。毕竟,通过将Int替换为_,只不过节省了两个字符,这不像手动指定像Int这样的简单类型一开始就有问题。

但现在让我们看看这个功能如何扩展到更复杂的类型,这就是它真正开始发光的地方。例如,假设项目包含以下功能,允许加载用户注释的PDF文件:

func loadAnnotatedPDF(named: String) -> Resource<PDF<UserAnnotations>> {
    ...
}

上述函数返回一个相当复杂的泛型类型,这可能是因为在多个域中重用Resource类型,也因为选择使用幻影类型来指定目前正在处理的PDF类型。

现在,如果在创建主题时调用上述函数,而不仅仅是使用一个简单的整数,之前基于CurrentValueSubject的代码会是什么样子:

// Before Swift 5.6:
let pdfSubject = CurrentValueSubject<Resource<PDF<UserAnnotations>>, Never>(
    loadAnnotatedPDF(named: name)
)

// Swift 5.6:
let pdfSubject = CurrentValueSubject<_, Never>(
    loadAnnotatedPDF(named: name)
)

这是一个巨大的进步!基于Swift 5.6的版本不仅节省了一些键入操作,而且由于pdfSubject类型现在完全来自loadAnnotatedPDF函数,这可能会使迭代该函数(及其相关代码)变得容易得多——因为如果更改该函数的返回类型,只有一些少量手动类型注释需要更新。

然而,值得指出的是,在上述情况下,还有另一种方法可以利用Swift的类型推断功能——那就是使用类型别名,而不是类型占位符。例如,以下定义一个UnfailingValueSubject类型别名,用它可以轻松创建无法抛出任何错误的主题:

typealias UnfailingValueSubject<T> = CurrentValueSubject<T, Never>

有了别名,现在将能够在没有任何范型类型注释的情况下创建pdfSubject——因为编译器能够推断T所指的类型,并且故障类型Never已被硬编码到新的类型别名中:

let pdfSubject = UnfailingValueSubject(loadAnnotatedPDF(named: name))

然而,这并不意味着类型别名普遍优于类型占位符,因为如果为每个特定情况定义新的类型别名,这也可能会使代码库变得更加复杂。有时,内联指定所有内容(例如使用类型占位符时)绝对是正确的选择,因为这可以让我们定义完全自包含的表达式。

在总结之前,先看看类型占位符如何与集合字面字面量一起使用——例如在创建字典时。在这里,我们选择手动指定字典的Key类型(以便能够使用点语法来引用枚举的各种情况), 而对其值使用类型占位符:

enum UserRole {
    case local
    case remote
}

let latestMessages: [UserRole: _] = [
    .local: “”,
    .remote: “”
]

这就是类型占位符——Swift 5.6中引入的一项新功能,在处理稍微复杂的泛型类型时,它可能非常有用。然而,值得指出的是,这些占位符只能在调用点上使用,而不是在指定函数或计算属性的返回类型时使用。

我希望您觉得这篇文章有用,如果您有任何问题、评论或反馈,请随时通过推特电子邮件告诉我。

感谢您的阅读!