多个变量参数,改进隐式成员语法,结果构建器等。
Swift 5.4带来了一些巨大的编译改进,包括更好地完成带错误的表达式中的代码,以及增量编译的大提速。不过,它也增加了一些重要的新特性和改进,让我们在这里深入了解一下......
- 小贴士:如果你想自己尝试 代码样本 ,也可以下载这个作为Xcode Playground。
改进了隐式成员语法
SE-0287改进了Swift使用隐式成员表达式的能力,所以你可以制作它们的链子,而不是只支持一个单一的静态成员。
Swift一直以来都有能力使用隐式成员语法来处理简单的表达式,例如,如果你想在SwiftUI中给一些文本着色,你可以使用.red而不是Color.red。
struct ContentView1: View {
var body: some View {
Text("You're not my supervisor!")
.foregroundColor(.red)
}
}
复制代码
在Swift 5.4之前,这在更复杂的表达式中是行不通的。例如,如果你想让你的红色略微透明,你就需要这样写。
struct ContentView2: View {
var body: some View {
Text("You're not my supervisor!")
.foregroundColor(Color.red.opacity(0.5))
}
}
复制代码
从Swift 5.4开始,编译器能够理解多个链式成员,这意味着可以推断出Color类型。
struct ContentView3: View {
var body: some View {
Text("You're not my supervisor!")
.foregroundColor(.red.opacity(0.5))
}
}
复制代码
函数中的多变量参数
SE-0284引入了让函数、下标和初始化器使用多个变量参数的能力,只要变量参数后面的所有参数都有标签。在Swift 5.4之前,这种情况下只能有一个变量参数。
所以,有了这个改进,我们可以写一个函数,接受一个变量参数,存储足球比赛中进球的次数,再加上第二个变量参数,打出进球球员的名字。
func summarizeGoals(times: Int..., players: String...) {
let joinedNames = ListFormatter.localizedString(byJoining: players)
let joinedTimes = ListFormatter.localizedString(byJoining: times.map(String.init))
print("\(times.count) goals where scored by \(joinedNames) at the follow minutes: \(joinedTimes)")
}
复制代码
要调用该函数,提供两组值作为变量参数,确保第一个变量之后的所有参数都有标签。
summarizeGoals(times: 18, 33, 55, 90, players: "Dani", "Jamie", "Roy")
复制代码
结果生成器
函数构建器在Swift 5.1中非正式地出现了,但在Swift 5.4之前,它们作为SE-0289正式通过了Swift进化提案过程,以便进行讨论和完善。作为这个过程的一部分,它们被重新命名为结果构建器以更好地反映它们的实际目的,甚至获得了一些新的功能。
首先是最重要的部分:结果构建器允许我们通过在我们选择的序列中一步步地创造一个新的价值。它们为SwiftUI的视图创建系统的很大一部分提供了动力,所以当我们拥有一个内部有各种视图的VStack时,Swift会默默地将它们归为一个内部的TupleView类型,这样它们就可以作为VStack的一个子代来存储--它将一个视图序列变成了一个视图。
结果构建器应该有自己的详细文章,但我至少想给你一些小的代码示例,这样你就可以看到它们的运作。
下面是一个返回单个字符串的函数。
func makeSentence1() -> String {
"Why settle for a Duke when you can have a Prince?"
}
print(makeSentence1())
复制代码
这很好用,但如果有几个字符串我们想连接在一起呢?就像SwiftUI一样,我们可能想把它们都单独提供,然后让Swift来解决。
// This is invalid Swift, and will not compile.
// func makeSentence2() -> String {
// "Why settle for a Duke"
// "when you can have"
// "a Prince?"
// }
复制代码
就其本身而言,这段代码是行不通的,因为Swift不再理解我们的意思。然而,我们可以创建一个结果生成器,它可以理解如何使用我们想要的任何转换将几个字符串转换为一个字符串,就像这样。
@resultBuilder
struct SimpleStringBuilder {
static func buildBlock(_ parts: String...) -> String {
parts.joined(separator: "\n")
}
}
复制代码
尽管这只是少量的代码,但有很多需要解压。
- @resultBuilder属性告诉SwiftUI以下类型应该被视为结果构建器。以前这种行为是通过@_functionBuilder实现的,它有一个下划线来表明这不是为一般使用而设计的。
- 每个结果构建器都必须提供至少一个名为buildBlock()的静态方法,该方法应该接收某种数据并对其进行转换。上面的例子是接收零个或多个字符串,将它们连接起来,然后以单个字符串的形式发送回来。
- 最终的结果是,我们的SimpleStringBuilder结构变成了一个结果构建器,这意味着我们可以在任何需要它的字符串连接能力的地方使用@SimpleStringBuilder。
没有什么可以阻止我们直接使用SimpleStringBuilder.buildBlock(),就像这样。
let joined = SimpleStringBuilder.buildBlock(
"Why settle for a Duke",
"when you can have",
"a Prince?"
)
print(joined)
复制代码
然而,由于我们在SimpleStringBuilder结构中使用了@resultBuilder注解,我们也可以将其应用到函数中,就像这样。
@SimpleStringBuilder func makeSentence3() -> String {
"Why settle for a Duke"
"when you can have"
"a Prince?"
}
print(makeSentence3())
复制代码
请注意,我们不再需要每个字符串末尾的逗号--@resultBuilder通过使用SimpleStringBuilder自动将makeSentence()中的每个语句转换为单个字符串。
在实践中,结果生成器能够做得更多,通过向你的生成器类型添加更多的方法来完成。例如,我们可以通过添加两个额外的方法来为我们的SimpleStringBuilder添加if/else支持,这两个方法描述了我们要如何转换数据。在我们的代码中,我们根本不想转换我们的字符串,所以我们可以直接把它们送回去。
@resultBuilder
struct ConditionalStringBuilder {
static func buildBlock(_ parts: String...) -> String {
parts.joined(separator: "\n")
}
static func buildEither(first component: String) -> String {
return component
}
static func buildEither(second component: String) -> String {
return component
}
}
复制代码
我知道,看起来我们几乎没有做任何工作,但现在我们的函数能够使用条件。
@ConditionalStringBuilder func makeSentence4() -> String {
"Why settle for a Duke"
"when you can have"
if Bool.random() {
"a Prince?"
} else {
"a King?"
}
}
print(makeSentence4())
复制代码
同样,我们也可以通过在构建器类型中添加 buildArray() 方法来添加对循环的支持。
@resultBuilder
struct ComplexStringBuilder {
static func buildBlock(_ parts: String...) -> String {
parts.joined(separator: "\n")
}
static func buildEither(first component: String) -> String {
return component
}
static func buildEither(second component: String) -> String {
return component
}
static func buildArray(_ components: [String]) -> String {
components.joined(separator: "\n")
}
}
复制代码
现在我们可以使用for循环。
@ComplexStringBuilder func countDown() -> String {
for i in (0...10).reversed() {
"\(i)…"
}
"Lift off!"
}
print(countDown())
复制代码
这感觉几乎就像魔法一样,因为结果构建器系统几乎为我们做了所有的工作,尽管我们的例子已经相当简单,但我希望你能感受到结果构建器给Swift带来的非凡力量。
值得补充的是,Swift 5.4扩展了结果构建器系统,支持属性被放置在存储的属性上,这将自动调整结构的隐式成员初始化器来应用结果构建器。
这对于使用结果构建器的自定义SwiftUI视图特别有帮助,比如这个视图。
struct CustomVStack<Content