多个尾随闭包,大规模的包管理器改进等等。
Swift 5.3为Swift带来了另一系列改进,包括一些强大的新功能,如多模式捕获子句和多个尾随闭包,以及Swift Package Manager的一些重要更改。
在本文中,我将介绍每一个主要的变化,同时提供实践代码示例,以便您可以自己尝试。我鼓励您通过Swift Evolution提案的链接了解更多信息,如果您错过了我之前在**Swift 5.2文章中的**新内容,请也查看一下。
多模式捕获条款
**SE-0276**引入了在单个捕获块内捕获多个错误案例的能力,这允许我们在错误处理中删除一些重复。
例如,我们可能有一些代码为一个错误定义了两个枚举情况:
enum TemperatureError: Error { case tooCold, tooHot}
当读取某物的温度时,我们可以抛出其中一个错误,或者发回“确定”:
func getReactorTemperature() -> Int { 90}func checkReactorOperational() throws -> String { let temp = getReactorTemperature() if temp < 10 { throw TemperatureError.tooCold } else if temp > 90 { throw TemperatureError.tooHot } else { return "OK" }}
当要捕捉抛出的错误时,SE-0276让我们用逗号分隔它们,以同样的方式处理太热和太冷:
do { let result = try checkReactorOperational() print("Result: \(result)")} catch TemperatureError.tooHot, TemperatureError.tooCold { print("Shut down the reactor!")} catch { print("An unknown error occurred.")}
您可以根据需要处理任意多的错误案例,如果需要,您甚至可以从错误中绑定值。
多个尾随闭包
**SE-0279**引入了多个尾随闭包,从而提供了一种更简单的方法来调用具有多个闭包的函数。
这在Swift UI中尤其受欢迎,其中代码如下:
struct OldContentView: View { @State private var showOptions = false var body: some View { Button(action: { self.showOptions.toggle() }) { Image(systemName: "gear") } }}
现在可以这样写:
struct NewContentView: View { @State private var showOptions = false var body: some View { Button { self.showOptions.toggle() } label: { Image(systemName: "gear") } }}
从技术上讲,标签:不需要和前面的}在同一行,所以如果你愿意,你甚至可以写这个:
struct BadContentView: View { @State private var showOptions = false var body: some View { Button { self.showOptions.toggle() } label: { Image(systemName: "gear") } }}
然而,为了可读性,我要警告不要这样做——像这样的浮动代码永远不会令人愉快,在Swift中,它看起来像一个标记的块,而不是按钮初始化器的第二个参数。
**注意:**在Swift论坛上有很多关于多次跟踪关闭的热烈讨论,我想借此机会提醒人们在参与我们的社区时要文明。像这样值得注意的语法变化一开始总是很奇怪,但是请给它时间,看看你在实践中进展如何。
枚举的综合可比一致性
**SE-0266**允许我们为没有关联值或关联值本身是可比的枚举选择可比一致性。这允许我们使用<、>和相似来比较同一个枚举中的两个情况。
例如,如果我们有一个描述服装尺寸的枚举,我们可以让斯威夫特合成这样的可比一致性:
enum Size: Comparable { case small case medium case large case extraLarge}
我们现在可以创建该枚举的两个实例,并使用<进行比较,如下所示:
let shirtSize = Size.smalllet personSize = Size.largeif shirtSize < personSize { print("That shirt is too small")}
这种综合的一致性对于可比的相关值非常有效。例如,如果我们有一个枚举描述了一个球队赢得足球世界杯,我们可以这样写:
enum WorldCupResult: Comparable { case neverWon case winner(stars: Int)}
然后,我们可以创建具有不同值的枚举的几个实例,并让Swift对它们进行排序:
let americanMen = WorldCupResult.neverWonlet americanWomen = WorldCupResult.winner(stars: 4)let japaneseMen = WorldCupResult.neverWonlet japaneseWomen = WorldCupResult.winner(stars: 1)let teams = [americanMen, americanWomen, japaneseMen, japaneseWomen]let sortedByWins = teams.sorted()print(sortedByWins)
这将对排列进行排序,以便两个没有赢得世界杯的球队排在第一位,然后是日本女队,然后是美国女队——它认为两个胜利者的情况高于两个从未赢过的情况,并认为胜利者(星星: 4)高于胜利者(星星: 1)。
自我在许多地方不再需要
**SE-0269**允许我们在许多不必要的地方停止使用自己。在这个变化之前,我们需要写自我。在任何引用自我的闭包中,所以我们将捕获语义学显式化,但是我们的闭包通常不能导致引用循环,这意味着自我只是杂乱的。
例如,在此更改之前,我们会编写如下代码:
struct OldContentView: View { var body: some View { List(1..<5) { number in self.cell(for: number) } } func cell(for number: Int) -> some View { Text("Cell \(number)") }}
对self.cell(for:)的调用不能导致引用周期,因为它是在结构中使用的。多亏了SE-0269,我们现在可以像这样编写相同的代码:
struct NewContentView: View { var body: some View { List(1..<5) { number in cell(for: number) } } func cell(for number: Int) -> some View { Text("Cell \(number)") }}
这可能在任何大量使用闭包的框架中非常流行,包括Swift UI和合并。
基于类型的程序入口点
**SE-0281**引入了一个新的@main属性,允许我们声明程序的入口点在哪里。这允许我们准确地控制代码的哪一部分应该开始运行,这对命令行程序特别有用。
例如,以前创建终端应用时,我们需要创建一个名为main.swift的文件,该文件能够引导我们的代码:
struct OldApp { func run() { print("Running!") }}let app = OldApp()app.run()
Swift自动将main.swift中的代码视为顶级代码,因此它将创建应用实例并运行它。即使在SE-0281之后,情况仍然如此,但是现在如果您愿意,您可以删除main.swift,而是使用@main属性来标记包含静态main()方法的结构或基类,该方法将用作程序的入口点:
@mainstruct NewApp { static func main() { print("Running!") }}
运行时,Swift将自动调用NewApp.main()来启动代码。
UI Kit和App Kit开发人员将熟悉新的@main属性,我们使用@UI Application Main和@NS Application Main来标记我们的应用委托。
然而,在使用@main时,您应该注意一些附带条件:
-
不能在已经有main.swift文件的应用中使用此属性。
-
你不能有一个以上的@main属性
-
@main属性只能应用于基类–它不会被任何子类继承。
上下文泛型声明的子句
**SE-0267**引入了在泛型类型和扩展中向函数附加here子句的功能。
例如,我们可以从一个简单的Stack结构开始,它允许我们从私有数组中推送和弹出值:
struct Stack<Element> { private var array = [Element]() mutating func push(_ obj: Element) { array.append(obj) } mutating func pop() -> Element? { array.popLast() }}
使用SE-0267,我们可以向该堆栈添加一个新的排序()方法,但仅限于堆栈中的元素符合可比:
extension Stack { func sorted() -> [Element] where Element: Comparable { array.sorted() }}
列举案件作为协议证人
SE-0280允许枚举参与协议见证匹配,这是一种技术方式,表示它们现在可以更容易地匹配协议的要求。
例如,您可以编写代码来处理各种类型的数据,但是如果这些数据丢失了呢?当然,您可以使用类似nil coalesc ing的东西来每次提供默认值,但是您也可以创建一个需要默认值的协议,然后使各种类型符合您想要的默认值:
protocol Defaultable { static var defaultValue: Self { get }}extension Int: Defaultable { static var defaultValue: Int { 0 }}extension Array: Defaultable { static var defaultValue: Array { [] }}extension Dictionary: Defaultable { static var defaultValue: Dictionary { [:] }}
SE-0280允许我们做的与枚举完全相同。例如,您想创建一个填充枚举,它可以采用一定数量的像素、一定数量的厘米或系统决定的默认值:
enum Padding: Defaultable { case pixels(Int) case cm(Int) case defaultValue}
在SE-0280之前,这种代码是不可能的——斯威夫特会说填充不满足协议。然而,如果你认为它通过协议真的很满意:我们说它需要一个返回Self的静态默认值,即任何符合协议的具体类型,这正是Padding. default Value所做的。
精化didSet语义
SE-0268调整didSet属性观察员的工作方式,使其更有效。这不需要代码更改,除非您以某种方式依赖于以前的错误行为;你只会免费得到一个小小的性能改进。
在内部,这种变化使得Swift在您没有使用旧值的任何情况下设置新值时都不会检索以前的值,如果您没有引用old Value并且没有意愿Set Swift将就地更改您的数据。
如果你碰巧依赖旧的行为,你可以通过引用old Value来触发你的自定义getter来解决它,就像这样:
didSet { _ = oldValue}
一个新的Float 16类型
SE-0277引入了一种新的半精度浮点类型,称为Float 16,它通常用于图形编程和机器学习。
这种新的浮点类型与Swift的其他类似类型一样:
let first: Float16 = 5let second: Float32 = 11let third: Float64 = 7let fourth: Float80 = 13
Swift Package Manager可获得二进制依赖项、资源等
Swift 5.3为Swift Package Manager(SPM)引入了许多改进。虽然不可能在这里给出这些例子,但我们至少可以讨论发生了什么变化以及原因。
首先,SE-0271(Package Manager Resources)允许SPM包含诸如图像、音频、JSON等资源。这不仅仅是将文件复制到一个完成的应用程序包中——例如,我们可以对我们的资产应用定制处理步骤,比如为iOS优化图像。这还添加了一个新的Bundle.module属性,用于在运行时访问这些资产。SE-0278(包管理器本地化资源)在此基础上构建,以允许本地化版本的资源,例如法语图像。
其次,SE-0272(Package Manager二进制依赖)允许SPM使用二进制包以及它对源代码包的现有支持。这意味着现在可以使用SPM集成常见的闭源SDK,如Firebase。
第三,SE-0273(Package Manager条件目标依赖项)允许我们将目标配置为仅具有特定平台和配置的依赖项。例如,我们可能会说,在为Linux编译时,我们需要一些特定的额外框架,或者在为本地测试编译时,我们应该构建一些调试代码。
值得补充的是,SE-0271的“未来方向”部分提到了对单个资源文件的类型安全访问的可能性——SPM能够为我们的资源文件生成特定的声明作为Swift代码,这意味着像图像(“化身”)这样的东西变成了Image(module.avatar)。
下一步在哪里?
我们希望在WWDC 20看到Swift 5.3的第一个测试版与Xcode Next一起发布,但与此同时,您可以从Swift.org.下载夜间工具链快照
我还建议您查看Swift标准库预览版——这篇文章的早期版本以SE-0270为特色,它在不连续的元素上添加了新的收集方法,但随后被移至库预览版。所以,快去试一试吧,看看你是怎么想的!