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中引入的一项新功能,在处理稍微复杂的泛型类型时,它可能非常有用。然而,值得指出的是,这些占位符只能在调用点上使用,而不是在指定函数或计算属性的返回类型时使用。
我希望您觉得这篇文章有用,如果您有任何问题、评论或反馈,请随时通过推特或电子邮件告诉我。
感谢您的阅读!