官方视频地址:https://developer.apple.com/videos/play/wwdc2017/402/
在这个近1个小时的视频里,我们看到了 Swift 4 的新特性,Swift 团队的方向以及开源社区的强大力量。
整个视频分为五个小节,分别为:
- 语言的优化与新功能(Language refinements and additions)
- 源码兼容性(Source compatibility)
- 工具与性能(Tools and performance)
- 标准库(Standard library)
- 内存的排他性存取(Exclusive access to memory)
下面我们就逐个解析吧。
一、语言的优化与新功能(Language refinements and additions)
-
extension
中可以访问到被private
修饰的变量。这避免了有时将一些逻辑分离到extension
中却访问不了相关的变量,然后不得不将private
改成fileprivate
的尴尬。😂 - 类与协议的组合。简单来说就是下面的样子:
var btns: [UIButton & MyProtocol]
用&
将类名和协议名组合起来,实现一些特定的能力。增加了POP的可用性,喜大普奔!同时,一些 OC 的 API 终于有了完美的 Swift 写法,如视频中的提及的:
二、源码兼容性
Swift 4 的与 Swift 3 的差别不大,至少不像从 Swift 2 到 3 这么大。这下大家可以安心升级到 Swift 4 了吧?
在 Xcode 9 中,Swift 会以 4 和 3.2 两个版本兼容存在。我们可以对不同的 target 选择不同的版本进行编译,比如:App 用 Swift 4 编译,而某些第三方库没有更新,依然可以用 Swift 3.2 来编译。
三、工具与性能
-
新的 Build System
更低的性能开销(特别是在大项目上)。使用新的 Build System 目前需要在 Project Settings / Workspace Settings 中手动开启,效果究竟如何有待商榷。
-
预编译的桥接头文件
在 Xcode 9 中,对混编项目的桥接头文件(bridging header)进行了预编译,生成了一个 precompiled header, 加快了对这些桥接头文件的解析速度。在大项目中这个优化尤为有效,因为我们不再需要在每个 Swift 文件编译时再对桥接头文件编译,也就是说,减少了无谓的、重复的编译。这项优化在 Xcode 9 中默认开启。
-
覆盖测试中使用共用构建(Shared Build for Coverage Testing)
在 Xcode 8 中,执行测试构建会令工程构建两次,其中一次是对整个工程的重新构建,以让其包含冗余测试代码 (extra instrumentation code)从而对代码片段的执行次数进行计数;而另一次构建则是普通的构建,不包含冗余测试代码。由于冗余测试代码带来的开销非常小(less than 3%),因此在 Xcode 9 中,编译器对此进行了优化,两次构建变成了一次构建。
-
构建时索引(Indexing While Building)
在 Xcode 9 中,烦人的索引过程被挪到了构建时才执行。同样开销非常小。每次构建将更新索引,以实现更精准的索引结果。终于可以跟它说滚蛋了:
-
可预测的性能(Predictable Performance)
如果你看过去年 WWDC Session 416 Understanding Swift Performance 的话,应该知道什么是存在容器(Existential Container)。
简单来说,就是协议类型在数组等集合类型中的数据结构。在存在容器中有一个缓存区,占 3 Words 的大小,在64位系统中就是 8 * 3 = 24 Bytes。如果存在容器装得下某个小型的数据结构(比如有2个 Double 的 struct),那这个结构就会被存储在缓存区里;否则会分配到堆中,然后将一个指向该堆地址的指针存储在缓存区(栈)里。
于是就有了下面这张图:4 Words 的 struct 对比只有 1、2、3 Words 的 struct,会有一个性能开销迅速爬升的情况(performance cliff)。这就是因为涉及到了堆内存的分配。
视频中还提到:“我们正在重新考量这个内联缓存区的大小,但在 Swift 4 中,它依然是过去一样占 3 个 Words 的大小”。
-
COW 存在容器
”那有 performance cliff 怎么办呢?“
” 用🐮牛X版存在容器!“
为了解决这个问题,Swift 团队优化了存在容器,使其有了 COW(copy-on-write,写时复制)能力。如此一来,分配在堆中的缓存区也有了引用计数,多个存在容器可以共用一个缓存区。当要写内存时再根据 COW 的规则来走,减少了堆内存开销。 因此在 Swift 4 中,存在容器将有一个更稳定可靠的性能表现。
另外,对于泛型也有一项优化:将未具体化的泛型代码(unspecialized generic code)所用的泛型缓存区(generic buffer)的内存分配位置,从原来的堆改成了 Swift 4 中的栈,以实现跟COW存在容器相似的优化效果。
-
更小的二进制包大小(Smaller Binaries)
减小二进制包大小也就意味着用户的 App 更小。Swift 4 中有这几个方面的优化让我们的 App 瘦身:移除未使用的(协议)实现代码。Swift 4 的编译器可分析出哪些(协议)实现代码是没有被使用的,从而在打包时移除掉这些代码。然而这个分析+移除的策略还不是那么完美,需要改动一下 Swift 的语法,也就是下面一项。
显式写 @objc 关键字以避免自动生成 Objective-C thunk 函数。原来啊,Swift 3 编译时会帮 NSObject 的子类构建一份Objective-C 版本的 thunk 函数以供其 runtime 时调用。这些 thunk 函数虽 然最终还是调用 Swift 的实现,但也占二进制大小,还让(1)中的优化无法实现。因此在 Swift 4 + Objective-C 的混编项目中,需要显式写 @objc 关键字,暴露需要用到的 Swift 方法。如果有多个方法要暴露给 Objective-C,建议你把它们放入 @objc extension 中。
-
剥除 Swift 符号 。
Swift 团队为 libswiftCore 等核心库减小了 symbol 的大小占用,方法是使用更简洁的命名和剥除 Swift 符号。
至于剥除符号(Symbol Stripping)的原因,请允许我翻译一下视频中的原话:
"静态链接器和动态链接器分别用自己的字典树来快速查找符号,也就是说 Swift 的 symbols 放在符号表中是基本上没用的。"
因此在 Xcode 9 中,Strip Swift Symbols
默认开启以优化我们工程中自己的 Swift 代码;而对于系统库的 Swift 代码,则在 App Thinning 中才进行符号剥离,同样有一个Strip Swift Symbols
的选项可供勾选。
四、标准库
这一小节主要讲了Swift 字符串的优化、一些新语法和新的泛型特性。
-
Swift 4 处理复杂字素将更快(是 Swift 3 的3倍效率)。在 Swift 中,一个
Character
即一个字素(Grapheme)。所谓的“复杂字素”,从表面上看,是指如emoji、拉丁字母、汉字、日语假名等非英文亦非简单字符的字素;从底层看,是那些无法通过下标随机存取的字符集合。像这样:var famaily = "👩" famaily += "\u{200D}👩" famaily += "\u{200D}👧" famaily += "\u{200D}👦" print(famaily) // 👩👩👧👦 print(famaily.characters.count) // Swift3输出“4”,Swift4输出“1“
Swift 4 的字符串本身就是一个字符集合。也就是说,不需要再写
str.characters.count
了,直接str.count
。-
一个新语法:
str.startIndex...
,表示从str
的startIndex
到其endIndex
。再举个粟子:
-
截取部分字符串的 API 变了,返回的类型也变了。
let str = "one,two,three" // Swift 3 str.components(separatedBy: ",") // 返回 Array<String> // Swift 4 str.split(separator: ",") // 返回新类型 Array<Substring>
此举有利有弊。
利:不再执行深拷贝,减少内存分配和性能开销;
弊:原来的字符串大哥有可能被其切片后的小弟保持强引用,导致大哥释放不了。
因此,要在适当的时候显式将
Substring
转成String
!
- 使用一对三双引(""")来包装多行字符串字面量。如下图所示,注意缩进:(开源的力量!👏)
新的泛型特性:
protocol
的associatedtype
可以使用where
子句,以实现对不同associatedtype
之间的约束。视频给出了 Swift 标准库中利用这个新特性优化了Sequence
和Collection
的案例。-
新的泛型特性:泛型下标(Generic Subscripts)。也就是说可以这样:
extension Bar { subscript<T>(t: T) -> Foo { //... } }
视频给出了 Swift 标准库中利用这个新特性实现了
…
(上面第3点)的案例。
五、内存的排他性存取(Exclusive access to memory)
这是一个 Swift 4 的新特性,简单而言就是,对有值语义(value types)的集合类型变量的写操作时,不能同时再触发另一读写操作。或者也可以理解成,不能再触发 copy-on-write 机制。
也就是说,不让下面这种事发生!在迭代时,闭包引用了numbers
的缓存区,希望修改numbers
内元素的值。如果此时执行numbers = []
,那就会报错:排他性存取!
排他性存取的检测分为编译时和运行时两种。编译时检测可直接报错;运行时检测则会抛出异常,如下图。
有点像多线程的问题对吧?至此,我们只是在单线程下看这个排他性存取,而如果在多线程下触发运行时的排他性存取,那就要通过 Thread Sanitizer 处理了。
当前,在 Swift 3.2 下的非排他性存取只会报 warning,但在未来的 Xcode 中会升级为 error。
内存的排他性存取保证了安全性的同时,也为从中优化了标准库的设计。
在工程设置中可以调整 Exclusive Access to Memory 的策略。
总结
Swift 4 在 Swift 3 的基础上升级,没有像去年那样巨大的迁移成本。升级后的 Swift 更快速、更安全,配合着 Xcode 9,生成的 App 体积将更小。
最后
此文粗略,或有错漏,烦请指明,当天修正!
终于写完了!全程无字幕听着画重点,哈哈!🐶
Let's Swift!🎉