欢迎关注微信公众号「Swift 花园」
Swift 5.3 有不少变化,这其中包括多模式 catch 语句,多拖尾闭包,以及 Swift Package Manager 的一些重要改变。
本文会带你浏览一些主要的变化,同时提供参考代码,以便你可以自行尝试。以下是要介绍的新特性的清单:
- 多模式 catch 语句
- 多拖尾闭包
- 为枚举自动生成的 Comparable 实现
- self. 书写省略
- 基于类型的程序入口
- 基于上下文泛型声明的
where
语句 - 枚举的 cases 可以作为 protocol witnesses
- 重新提炼的
didSet
语义 - 新的 Float16 类型
- Swift Package Manager 支持二进制依赖,资源等更多类型
多模式 catch 语句
SE-0276 引入了一个可以在单个 catch
块中捕获多个错误 case 的特性,这能让我们免除错误处理时的重复代码。
例如,下面的代码用枚举定义了错误的两种情况:
1 | enum TemperatureError: Error { |
当我们读取到温度时,既可以抛出两种错误中的某一个,也可以返回 “OK”:
1 | func getReactorTemperature() -> Int { |
在捕获错误的环节,SE-0276 允许我们用逗号分隔来表示我们要以相同方式处理 tooHot
和 tooCold
。
1 | do { |
处理的 case 可以是任意数量的。
多拖尾闭包
SE-0279 引入了多拖尾闭包,这使得调用包含多个闭包的函数可以更简单地实现。
这个特性在 SwiftUI 中非常受欢迎。原来形如下面这样的代码:
1 | struct OldContentView: View { |
可以被改写成:
1 | struct NewContentView: View { |
理论上并不要求 label:
要跟在前一个闭包的 }
后面,所以你甚至可以像下面这样书写:
1 | struct BadContentView: View { |
不过,我会建议你当心代码的可读性 —— 像上面的 label
那样的代码块,在 Swift 里看起来更像是标签化的代码块,而不是 Button
构造器的第二个参数。
注: 有关 Swift 的多拖尾闭包特性的讨论非常热烈。我想提醒大家的是,像这种类型的语法变动一开始看起来可能会有点别扭,我们需要耐心,给它时间,在实践中体会它带来的结果。
为枚举自动生成的 Comparable 实现
SE-0266 使得我们可以为枚举生成 Comparable
实现,同时不要求我们声明关联值,或者要求关联值本身必须是 Comparable
的。这个特性让我们可以在同类型的枚举之间用 <
,>
和类似的比较操作符来进行比较。
例如,假设我们有一个枚举,它描述了衣服的尺寸,我们可以要求 Swift 为它自动生成 Comparable
实现,代码如下:
1 | enum Size: Comparable { |
然后我们就可以创建两个这个枚举的实例,并且用 <
进行比较:
1 | let shirtSize = Size.small |
自动生成的实现,也能很好地适应枚举的 Comparable
关联值。例如,假设我们有一个枚举,描述了某个队伍获取世界杯冠军的次数,代码可以这样实现:
1 | enum WorldCupResult: Comparable { |
然后我们用不同的值来创建枚举的不同实例,并且让 Swift 对它们进行排序:
1 | let americanMen = WorldCupResult.neverWon |
排序过程会把未获得世界杯冠军的队伍放在前面,然后是日本女子队,再然后是美国女子队 —— 两组 winner
的队被认为是大于两组 neverWon
的队,而 winner (stars: 4)
被认为是大于 winner (stars: 1)
。
self. 书写省略
SE-0269 使得我们可以在一些不必要的地方省略 self
。在这个改变之前,我们需要在所有的闭包当中对引用 self
的属性或者方法冠以 self.
,以便显式地明确语义。但有的时候由于闭包不可能产生引用循环,self
是多余的。
例如,之前我们需要把代码写成下面这样:
1 | struct OldContentView: View { |
对 self.cell (for:)
的调用不会产生引用循环,因为它是在结构体内使用。多亏了 SE-0269,上面的代码现在可以免去 self.
:
1 | struct NewContentView: View { |
这个特性对于大量使用闭包的框架非常有用,包括 SwiftUI 和 Combine。
基于类型的程序入口
SE-0281 引入了一个新的 @main
属性,它可以让我们声明程序的入口。这个特性使得我们可以精确地控制程序启动时要执行的代码,对于命令行程序尤其有帮助。
例如,当我们创建一个终端应用时,我们必须创建一个叫 main.swift 的文件,然后把启动代码放在里面:
1 | struct OldApp { |
Swift 会自动把 main.swift 看作最顶层的代码,创建 App
实例并且运行。即便在 SE-0281 之后这个做法都一直被延续,但现在你可以干掉 main.swift 了,转而使用 @main
属性来标记某个包含静态 main
方法的结构体或者类,让它充当程序入口:
1 |
|
上面的代码所在的程序运行时,Swift 会自动调用 NewApp.main ()
来启动程序流程。
新的 @main
属性对于 UIKit 和 AppKit 开发者来说可能有点属性,因为我们正是用 @UIApplicationMain
和 @NSApplicationMain
来标记 app 代理的。
不过,使用 @main
的时候有一些注意事项:
- 已经有 main.swift 文件的 app 不能使用这个属性
- 不能有一个以上的
@main
属性 @main
属性只能用在最顶层的类型上 —— 这个类型不继承自任何其他类
基于上下文泛型声明的 where
语句
SE-0267 引入一个新特性,你可以给泛型类型或者扩展添加带有 where
语句限定的函数。
例如,我们创建了一个简单的 Stack
,可以压栈,出栈元素:
1 | struct Stack<Element> { |
借助 SE-0267,我们现在可以添加一个 sorted ()
方法给这个 Stack
,并且要求这个方法只有在 Stack
的泛型参数 Element
遵循 Comparable
协议的时候才能使用:
1 | extension Stack { |
枚举的 cases 可以作为 protocol witnesses
SE-0280 使得枚举可以参与 protocol witness matching,这是一种表述我们可以更容易地匹配协议要求的技术方式。
例如,你可以编写代码处理各种类型的数据,但是假如数据不见了怎么办呢?当然,你可以借助空合运算符,每次都提供一个默认值。不过。你可以借助协议来要求默认值,然后让各种类型遵循这个协议:
1 | protocol Defaultable { |
SE-0280 使得我们能对枚举做出一样的控制。比如,你有一个 padding
枚举,它能接收像素值,厘米值,或者是系统的默认值:
1 | enum Padding: Defaultable { |
这样的代码在 SE-0280 之前是无法实现的 —— Swift 会抱怨 Padding
不满足协议。但是,如果你仔细琢磨一下,协议其实是满足的:我们需要一个静态的 defaultValue
,它返回 Self
,换言之,就是某个遵循协议的具体类型,而这正是 Padding.defaultValue
提供的。
重新提炼的 didSet
语义
SE-0268 调整了 didSet
属性观察者的工作方式,以便它们能更高效地工作。对于这个优化你不需要改动任何代码,自动获得一个小小的性能提升。
在内部,Swift 做出的改变是在设置新值时不再查询旧值。如果你不使用旧值,也没有设置 willSet
,Swift 会即时修改数值。
假如你需要用到旧值,只需要引用 oldValue
即可,方式如下:
1 | didSet { |
新的 Float16 类型
SE-0277 引入了一个新的半精度浮点类型,Float16
。这个精度在图像编程和机器学习中十分常见。
新类型和 Swift 原来的其他类型相似:
1 | let first: Float16 = 5 |
Swift Package Manager 支持二进制依赖,资源等更多类型
Swift 5.3 为 Swift Package Manager (SPM) 带来了很多提升,恕我不能在这里一一举例。不过我们可以讨论一下 SPM 有哪些变化以及为什么会有这些变化。
首先,SE-0271 (Package Manager Resources) 使得 SPM 能包含诸如图片,音频,JSON 等类型的资源。这个机制可不只是把文件拷进最终的 app bundle 这么简单 —— 举个例子,我们可以应用一个自定义处理步骤到我们的 assets,比如为 iOS 优化图片。为此,新增的 Bundle.module
属性就是用来在运行时访问这些 assets 的。SE-0278 (Package Manager Localized Resources) 进一步支持了资源的本地化版本,例如提供适用某个国家的图片。
其次,SE-0272 (Package Manager Binary Dependencies) 使得 SPM 可以使用二进制包。这意味着像 Firebase 这样的闭源 SDK 现在也可以通过 SPM 集成了。
再次,SE-0273 (Package Manager Conditional Target Dependencies) 可以让我们指定为特定平台和配置使用依赖。例如,我们可能在为 Linux 平台编译时,额外需要某些特定的框架,或者我们在本地测试的时候需要一些依赖调试用的框架。
值得一提的是,SE-0271 的 “Future Directions” 一节中提到了对资源的安全访问 —— 这意味着像 Image ("avatar")
这样的代码之后会变成 Image (module.avatar)
。