WWDC22 - Swift 5 新特性

| Swift , WWDC

 

内容概览

  • 前言
  • 社区更新
  • Swift 包
  • 性能提升
  • 并发更新
  • 表达力增强
  • 总结

 

 

前言

 

Swift 5 又经历了几次大迭代,截至 WWDC 22,Swift 的最新版本是 5.7

在 Swift 迈向下一个大版本 Swift 6 之前,Swift 5 已经进化出了原生的并发模型,而 Swift 6 将会在此基础上去实现完全线程安全的并发编程。

现在,和 Ficow 一起来看看,Swift 5 中有哪些特性可以帮助我们更快更安全地搬砖吧~

 

 

社区更新

 

开源社区

之所以 Swift 发展得这么快,还是多亏了开源社区的贡献呢。

今年,有了 DocC 和开源的 Swift.org 网站,更多项目出现在开源社区中。

Swift on Server 和 Diversity in Swift 两个工作组的工作模式运作良好,现在也会被运用到这两个工作组中:

Diversity in Swift 还引入了 Swift Mentorship Program(辅导项目),帮助新人快速上手相关领域的内容。如果你感兴趣,也可以考虑加入呢~

更多详情,请参考:
Celebrating learning experiences from the 2021 Swift Mentorship Program
Diversity in Swift

跨平台

为了更好地支持 Swift 跨平台,Swift 取出来外部的 Unicode 支持库,改用原生实现。如此一来,Swift 安装包更小也更快。

就连苹果自己的安全子系统 Secure Enclave 也是基于 Swift 开发的。

Amazon Linux 2 和 CentOS 7 可以直接通过 RPM 来安装 Swift。

 

 

Swift 包

 

TOFU

现在,Swift 很重视包管理,而且引入了一个新的概念

TOFU:Trust On First Use

这其实是一个安全协议,Swift 包被下载完成之后,包的指纹会被记录下来。以后的下载会验证指纹,如果指纹不一样就会报错。

这样,你可以更放心地使用 Swift 包。

命令插件(Command plugins)

命令插件可以用来生成文档、代码再格式化、生成测试报告等等。现在,你自己可以用 Swift 来写代码格式化工具和代码检查工具呢。

命令插件是介于开源工具和Swift包管理工具之间的胶水。

DocC 可以用来给源码集成文档,我们可以通过命令插件来调用它:

操作步骤:

  1. 创建一个 struct,然后遵循 CommandPlugin 协议;
  2. 在协议方法中调用你要使用的工具即可;

当你定义好命令插件之后,你可以通过命令行执行 SPM 命令来调用它,也可以通过 Xcode 的菜单。

构建工具插件(Build tool plugins)

构建工具插件允许你在构建项目时执行额外的步骤,比如:代码生成、文件处理。

构建工具插件大致的包组织形式如下:

插件的实现主要确定需要执行什么命令,以及最终产物输出到哪里,代码大致如下:

相关内容,请参考:
Meet Swift Package plugins
Create Swift Package plugins

模块别名

使用 Swift 包,可能会遇到模块重名的问题:

Swift 5.7 引入了模块别名:

现在,你可以这样导入之前的重名包:

 

 

性能提升

 

构建性能

Swift Driver 主要用于协调 Swift 源码编译过程。2021年被重写之后,性能有了显著的提升:

  • 集成到编译器中
  • 编译更快
  • 链接更快

现在,它已经被用作 Swift 构建系统的一个框架,以便更好地完成并行编译。性能提升对比如下所示:

具体内容,请参考:
Demystify parallelization in Xcode builds

类型检查

2022年,苹果通过重写了泛型系统的一个关键部分,提升了类型检查的性能。这个部分负责生成协议和 where 语句的方法签名。

基于旧的实现,时间和空间使用率会根据协议的数量呈指数增长。。。

下面这段泛型代码, 在很多关联类型上面增加了一些泛型约束,旧的实现(Swift 5.6)需要 17 秒 去检查类型:


而 Swift 5.7 只需要不到 1 秒 就可以完成!

运行时

在 Swift 5.7 之前,启动 App 就需要进行协议检查,在 iOS 上可能会长达 4 秒之久。

不过,现在协议被放到缓存了。在 iOS 16 上,平均的启动时间可能会减半。

你可以了解一下,如何更好地利用这些新特性:

Improve app size and runtime performance

 

 

并发更新

 

2022 年,Swift 引入了新的并发模型,以及 actorasync/await。相比于回调和手动队列管理,新的并发方式更安全也更易用。

为了兼容旧系统(iOS 13, macOS 10.15 Catalina, watchOS 6, tvOS 13),你的 app 会带上一份 Swift 5.5 的并发运行时副本。

回顾内存安全

一边读一边写,可能导致无法预料的行为:

上面的代码会移除 3,还是会清空整个数组? 都不是,编译器会报错~

数据竞争

和上面的例子类似,以下代码也会被报错:

这种情况下,就需要用到 actor 来解决数据竞争问题。

具体内容,请参考:

Eliminate data races using Swift Concurrency

Swift 5 实现了内存安全,现在还在完善并发模型,以及可选的类型检查,最终 Swift 6 会实现线程安全。

你可以在项目的构建设置中开启 更严格的并发检查:

distributed actor

Swift 甚至引入了分布式 actor,这样甚至可以很方便地在分布式环境下执行并发任务,并发代码的异常也很容易处理:

相关内容,请参考:
Meet distributed actors in Swift

异步算法包

  • 与 async/await 无缝集成
  • 支持使用 AsyncSequence 的基于时间的算法
  • 支持苹果、Linux、Windows

一些例子:

  • zip(),从多个异步序列的值创建元祖
  • merge(),合并多个异步序列为一个序列
  • debounce(),等待静默期过后再发送一个值
  • chunked(),根据时间或数量来将值分到不同的组中

相关内容,请参考:
Meet Swift Async Algorithms

并发优化

  • actor 优先级,先执行高优先级的任务(感觉很搞笑,这个不是理所应当的吗?这是修 bug 吧);
  • 避免优先级反转,低优先级的任务不会阻塞高优先级的任务(这个,好像也是理所应当的 😂);

并发测量工具 (Instrument)

一图胜千言!应该挺不错的,毕竟有比没有好嘛,哈哈:

相关内容,请参考:
Visualize and optimize Swift concurrency

 

 

表达力增强

 

如果你的手里有一把好用的锤子和一把很差劲的螺丝刀,你就会更喜欢去锤钉子,而不是去拧螺丝。

人类发明了工具,工具又会反过来塑造改变人类。Swift 也一样,如果它更好用,你就会更喜欢用它,它就越有可能变得更好。

可选解包

这种代码,你也经常写吧?

如果这种可选内容解包的变量变长,代码就开始变难看:

然后,就会有人给这种长长的变量起别名,然而这种代码可能会不好维护(改变量名的时候,忘记改这个别名):

庆幸的是,从 Swift 5.7 开始,你可以这样写了:

guard-let 也适用的喔:

闭包类型推断

现在,像这样复杂的闭包,你也不需要再显式地指定闭包的类型了:

指针用法的优化

有很多指针类型转换的语法,在 C 里面是没问题的,然而在 Swift 里面却会报错:

如果你在 Swift 里面用过指针,你可能也见过类似下面这样的错误吧?

然后,为了处理这种编译错误,我们就非要去纠正指针的类型:

不过,现在 Swift 5.7 针对 Swift 和 C 混编的代码做了一些优化,在 Swift 里面调用 C 的代码也会变得轻松很多:

用 Swift Regex 解析字符串

不用正则表达式来处理 Swift 字符串的代码就是这种样子,又长又难懂,不容易维护:

只是为了获取 git 提交者的名字和邮箱,上面这段代码重复用着字符串切片和索引。

还有更好的方式吗?首先,我们来拆解一下这个字符串:

正则表达式可以用声明式的语法来匹配这种结构:

然后,基于这个正则表达式,我们就可以写出这样的代码了:

但是,这个正则表达式不太容易理解,毕竟很多基础的正则表达式语法是需要理解和记忆的。即使是经验丰富的开发人员也需要花些时间才能搞清楚含义:

如果能把这些表达语法封装起来,那就会比较易用:

组合起来之后的用法,很简洁,像极了 SwiftUI:

完整的版本可以是这样的:

你甚至可以把这个正则表达式封装成一个组件:

请注意,你可以在 RegexBuilder 里面直接使用字符串常量:

也可以直接使用正则表达式常量:

还可以插入其他的类型,比如 Foundation 库里的日期格式类型:

不管你选择哪种正则写法,你都可以以一种简洁高效的方式来解析字符串:

不过,要用 Swift Regex,首先得确保系统版本达标:macOS 13, iOS 16, watchOS 9, tvOS 16

相关内容,请参考:
Meet Swift Regex
Swift Regex: Beyond the basics

泛型、协议语法优化

假设你要写一个 Git 客户端的界面,展示提交记录的时候需要用到字典来加速查询提交者姓名,编辑提交记录的时候需要用数组来保持原有的提交顺序:

然后,你还有一个协议(上面的两种类型都需要遵守它),以便于解析器添加任何一种类型的记录:

然而,现在解析器有两种实现方式来使用这个 Mailmap 协议:

上面的两个方法里用到的 Mailmap 协议,其实是有区别的():

  • 当协议名称用于继承列表、泛型形参列表、泛型实现约束、不透明结果类型,它的含义是 一个遵守此协议的实例
  • 当协议名称被用在,比如:实例类型,泛型实参,泛型同类型约束,函数的形参或返回类型,它的含义是 一个(装有一个遵守此协议的实例的)盒子

请参考下图中的示例代码理解上述区别:

这个区别非常重要,因为被引入的盒子需要占用更多的内存空间和计算时间,而且盒子不具备盒子里面的实例的所有能力。然而,我们往往很难分辨我们是否引入了这个盒子。

Swift 5.7 会用 any (不是 Any)来显式地标记盒子的使用,5.7 之前的版本会给出相应的警告提示。

现在,上面的类型定义就变成了这样:

这两个示例方法的差异也变得更明显,第一个方法传入泛型参数,第二个传入 any 类型的参数:

如果你打算将一个 any 类型的参数作为泛型参数传入,就会遇到一个清晰的错误:

传入的实例 b 其实是 any 类型的协议盒子,而 mergeEntries(from:) 方法其实需要传入一个遵守 Mailmap 协议的实例,但是 b 这个协议盒子其实不遵守 Mailmap 协议。

这时候,你就需要对这个盒子进行解包,先取出里面遵守了协议的实例,然后再传给这个泛型方法:

不过,像这种简单的使用场景,Swift 会自动帮你进行解包处理。所以,从 Swift 5.7 开始,你将很少再遇到这种错误。

有了这些优化项目,现在我们甚至可以很好地解决之前遇到的一些协议问题,比如:使用了 Self 类型的协议、有关联类型的协议或者遵守 Equatable 协议的协议。比如,你可能会遇到下面这样的错误:

不过,从 Swift 5.7 开始,这种错误就消失了!这可是困扰了大家许久的顽疾,终于,终于被干掉了!

现在,就连 Collection 这种复杂的协议,也可以被定义为 any 类型。
并且,由于引入了新特性 —— 主要的关联类型(primary associated types),MailmapEntry 也可以被定义为 any 类型:

下图中的 Element 是这个协议的用户都关心的关联类型,而 Index, Iterator 并不需要操心。通过将 Element 放入尖括号中,可以将其定义为主要的关联类型:

如果定义了主要的关联类型,你甚至可以用这种尖括号语法来约束协议的主要关联类型,即使是用在 any 类型中:

系统库中提供了实现同样目的的 AnyCollection 类型,这是一个用于擦除类型的封装类型。这个类型的实现就是一大堆模板代码,而 any 类型是语言特性,你可以很容易地使用。不过 AnyCollection 类型可以兼容旧的语言版本,而且比当前的 any 类型支持的特性更多。

如果你自己定义了一些类型擦除包装类:

现在可以考虑使用 any 类型来重写:

或者直接用类型别名:

不过,说了这么多好处,我们也还是要注意到这个问题:

这里,== 操作符要求 ab 是相同的类型。

所以,苹果官方还是建议你尽量少用 any 类型,推荐使用泛型!

让我们回到之前的例子来加深理解:

上面的方法使用了泛型,而下面的方法使用了 any 类型。前者会有更好的运行时性能,因为编译结束后,泛化的类型就具体化了;而后者每次都要对协议容器进行解包。

不过,any 类型的写法非常简洁,开发者们会更倾向于采用这种写法。为了帮助开发者们更好地用泛型,Swift 可以用 some 来约束泛型参数。

现在,泛型和协议写法都非常简洁了,不过泛型的运行时性能更好,优先采用 some 吧!

相关内容,请参考:
Embrace Swift generics
Design protocol interfaces in Swift

 

 

总结

 

不得不说,很多日常开发中遇到的难题都要从语言层面去优化才能更好的解决,比如:并发、正则表达式、泛型等。

本质上来说,这是一种编程思维模式上的切换,而不是头痛医头脚痛医脚。

以前,正则表达式是一个很多人都感觉很头痛的东西,甚至有开发朋友特地花时间去记忆正则表达式的语法。现在好了,Swift 已经给我们封装好了易用的接口,你甚至不需要懂正则表达式的语法都可以直接上手呢!

怎么样,Swift 5.7 推出的新特性和你的胃口吗?😄

 

参考内容:
What’s new in Xcode

 

觉得不错?点个赞呗~

本文链接:WWDC22 - Swift 5 新特性

转载声明:本站文章如无特别说明,皆为原创。转载请注明:Ficow Shen's Blog

评论区(期待你的留言)