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
:Trust On First Use
这其实是一个安全协议,Swift 包被下载完成之后,包的指纹会被记录下来。以后的下载会验证指纹,如果指纹不一样就会报错。
这样,你可以更放心地使用 Swift 包。
命令插件可以用来生成文档、代码再格式化、生成测试报告等等。现在,你自己可以用 Swift 来写代码格式化工具和代码检查工具呢。
命令插件是介于开源工具和Swift包管理工具之间的胶水。
DocC 可以用来给源码集成文档,我们可以通过命令插件来调用它:
操作步骤:
当你定义好命令插件之后,你可以通过命令行执行 SPM 命令来调用它,也可以通过 Xcode 的菜单。
构建工具插件允许你在构建项目时执行额外的步骤,比如:代码生成、文件处理。
构建工具插件大致的包组织形式如下:
插件的实现主要确定需要执行什么命令,以及最终产物输出到哪里,代码大致如下:
相关内容,请参考:
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 引入了新的并发模型,以及 actor
和 async/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 会实现线程安全。
你可以在项目的构建设置中开启 更严格的并发检查:
Swift 甚至引入了分布式 actor,这样甚至可以很方便地在分布式环境下执行并发任务,并发代码的异常也很容易处理:
相关内容,请参考:
Meet distributed actors in Swift
一些例子:
zip()
,从多个异步序列的值创建元祖merge()
,合并多个异步序列为一个序列debounce()
,等待静默期过后再发送一个值chunked()
,根据时间或数量来将值分到不同的组中相关内容,请参考:
Meet Swift Async Algorithms
一图胜千言!应该挺不错的,毕竟有比没有好嘛,哈哈:
相关内容,请参考:
Visualize and optimize Swift concurrency
如果你的手里有一把好用的锤子和一把很差劲的螺丝刀,你就会更喜欢去锤钉子,而不是去拧螺丝。
人类发明了工具,工具又会反过来塑造改变人类。Swift 也一样,如果它更好用,你就会更喜欢用它,它就越有可能变得更好。
这种代码,你也经常写吧?
如果这种可选内容解包的变量变长,代码就开始变难看:
然后,就会有人给这种长长的变量起别名,然而这种代码可能会不好维护(改变量名的时候,忘记改这个别名):
庆幸的是,从 Swift 5.7 开始,你可以这样写了:
guard-let
也适用的喔:
现在,像这样复杂的闭包,你也不需要再显式地指定闭包的类型了:
有很多指针类型转换的语法,在 C 里面是没问题的,然而在 Swift 里面却会报错:
如果你在 Swift 里面用过指针,你可能也见过类似下面这样的错误吧?
然后,为了处理这种编译错误,我们就非要去纠正指针的类型:
不过,现在 Swift 5.7 针对 Swift 和 C 混编的代码做了一些优化,在 Swift 里面调用 C 的代码也会变得轻松很多:
不用正则表达式来处理 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
类型来重写:
或者直接用类型别名:
不过,说了这么多好处,我们也还是要注意到这个问题:
这里,==
操作符要求 a
和 b
是相同的类型。
所以,苹果官方还是建议你尽量少用 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