WWDC17 — What's New in Swift

这几天正在开 WWDC,Swift 4.0 也要发布了,有点期待和好奇 4.0 版本有哪些改进和变化,所以我去官网看了一下它的 session。4.x 相对 3.x 版本的改变那是比那是比前几个版本小多了,特别是相比于从 3.x 对于 2.x 的改变来说。确实也应该如此,至少从语法上来说,以后 Swift 应该是会越来越稳定了。

记得去年的 What's New in Swift 还是 Chris Lattner 亲自来说的,那个时候他还在苹果,但是现在竟然跑特斯拉了。。

官网的 session 连英文字幕都没有,所以我看了一下也只能勉强听懂六七成。在这里我就随便扯一下我看了这次 session 之后的看明白或者印象比较深刻的地方吧。

访问权限控制

我记得在 3.x 的时候,Swift 增加了两种访问控制权限的关键词 fileprivateopen。原先只有 publicinternalprivate
internal 是默认的,可以省略,在模块内都可以访问,模块外不能访问到。
Swift 2.x 时代的 private 其实只是限制访问权限在当前文件内,而不只是本类才能访问,所以这是不太科学的。因此 Swift 3.x 增加了 fileprivate,顾名思义它表示在当前文件私有,它的作用其实就是原先 Swift 2.x private 的作用,这样的话,Swift 3.x 的 private 就可以是真正的私有了,只有在本类或者结构体中才能访问了。
open 则是为了完善 publicopenpublic 标记的方法变量或者类结构体等在模块外面也可以被访问到,区别就是 open 更加开放,public 修饰的在模块外面可以被访问,但不能被继承和覆写,但是 open 修饰的就可以。

上面是 Swift 3.x 的访问控制权限规则。对于 private 来说,它还有一个很不合理的地方,那就是被 private 修饰的变量或者方法,不能在本类的 extension 中被访问到。使用 extension 来拆分一个类或者结构体,使得类或者结构体的结构或者功能更加清晰,是 Swift 中十分方便也是十分提倡的一种做法。如果 private 的变量或者方法在拆分之后的 extension 中没法访问,那多么不方便。所以在 Swift 4.x 中,private的作用有了一些调整,使得在 extension 中也可以访问被其修饰的成员或者变量了。

下面是 session 中的例子

原先 Swfit 3.x 中,在 extension 中访问 private 修饰的变量是会报错的,要解决这个错误,可以将 private 改为 fileprivate,但这样子的话,这个变量就在整个文件中可见了,这样不是很合理。

现在 Swfit 4.x 中,这样的代码就不会报错了。

但是我还是有一个疑问,如果这个 extension 写在别的文件中,按照原先 Swfit 3.x 的规则,这时候使用 fileprivate 也无法访问这个变量了。不知道现在 Swfit 4.x 中 extension 和本类不在同一个文件,是否还支持访问 private 的变量方法。这个看来要以后自己测试一下才知道了。

类型共存

有这么一个例子,我们自定义了一个 protocal 里面有个 shake() 方法,然后让 UIButton 和 UISlider 实现了这个 protocal。然后我们有另外一个方法,在这个方法里面,我们要使用这个 shake() 方法。
因为我们并不是只有一个类实现了该协议,所以对于使用该类的对象,它的类型是什么呢?
因为 UIButton 和 UISlider 都是 UIControl 的子类,所以我们会想到将该类型设置为 UIControl,但这样是不对的,因为 UIControl 并没有实现该协议,所以它没有 shake() 方法。
我们也可以想到将该类型设置为该协议的类型,但这样也有问题,可以看到在这个例子中,对于使用 shake() 方法的对象 control,我们还是用了它的 state 属性,可是协议本身是没有这个属性的。
这就很尴尬了…
Swift 4.x 终于解决了这个问题,现在可以将两个类型用 & 来连接以前用了,估计就是类似逻辑“与”的效果,表示同时满足两种类型的类型变量。

兼容性

Swift 4.x 与 Swfit 3.x 的兼容性比之前版本要好得多,它不像 Swfit 2.x 过渡到 Swfit 3.x 那样,连基本的 API 接口全部变掉了。

这里面印象比较身的就是现在从 Swift 3.x 迁移到 Swift 4.x 可以选择特定的 target 来迁移了,而不是必须要整体迁移。这样做,可以使得你的工程依赖可以停留在 3.x 版本,而你的工程可以升级到 4.x 版本,两者可以共存。下次依赖升级到了 4.x 版本,你也一样可以将其迁移升级。

一些优化

Xcode 9 包含了一系列的优化,包含引入了新的 build system,对 Bridging Headers 预编译等等。

其中我印象比较深刻的是对于 Indexing 的优化。

Indexing 这玩意很有用,但在编译的时候他经常会出现,确实看着也很烦,经常要等它好一会它才会结束。现在 Xocde 9 对它进行了优化,以后不会这么频繁的出现了。

还有让我印象深刻的是对于二进制包的瘦身优化。

首先是减少无用代码,如果你用 Swfit 4 写了一些代码,但实际上没有被用到,那么编译器会忽略这些代码,这样最后打包出来的包就会小一些,对于大工程来说,无用代码可能比较多,人工又不能全检查出来,这个应该还是蛮有效果的。
另外一点就是限制 @objc 的使用,@objc 是用来将 Swfit 代码暴露给 Objective-C 使用的。

比如下面这个例子:

我们定义了一个类叫做 MyClass,它有两个方法。因为它继承自 NSObject,因此它就会暴露给 Objective-C。一般来说被 @objc 修饰的变量方法或者类等才会暴露给 Objective-C, 我在上面图片中给 @objc 加了红线划出来,我们写代码的时候其实是没有这个的,这个是编译器自动给我们加的。因为这个类继承自 NSObject ,所以它需要暴露给 OC,这样我们就需要生成 OC 的代码。编译器不知道这个方法最终有没有被调用,所以如果没有被 OC 调用,那生成出来的 OC 方法就多余了,所以才需要限制 @objc 的使用。

所以 Swift 4 规定只有真正需要的时候才可以使用 @objc,包括:覆写一个 OC 方法;实现一个 OC 协议。苹果说它这么做,使得 Apple Music 这个 App 的体积减小了 5.7%。

如果 Swift 中某些东西无法在 OC 中显示,例如下图中的 Int 类型 OC 中没有对应的类型,那么编译器也会报错,让我们不能直接使用 @objc

@objc 在 Swift 4 中使用不当,Xcode 9 同样会给出 warnning 或者在运行中控制台打印信息来提醒我们改进代码。

字符串改进

对于字符串的改进是我最期待的部分了。Swift 很优雅,很安全,但是对于字符串的处理真的挺麻烦的,虽然它这么做是为了提高语言的安全性和合理性。

原先我用 Swift 刷过一些 LeetCode 的题目,处理字符串真的不那么方便,相比之下 Python 就简单多了。

Swift 使用 grapheme 即“字形”的方式来表征字符串

一个字符串可以由多种方式来组合,最后显示出来的效果一致,在很多语言中,如果去比较这样两种不同组合方式形成的看起来一样的字符串,他们是不同的,例如在 Ruby 中:

但是在 Swfit 中,因为使用 grapheme 的方式来表征字符串,所以它们是相同的。

下面这个字符串由这么多个不同的字符串拼接而成,因为最后拼接出来的的字符串显示出来也只有“一个”,所以其 count 值也只有 1。

这样做对于字符串来说相比其他语言更加合理和严谨,但是也造成了对于字符串的处理比较麻烦,比如取字符串中的某个索引位置的字符,不能直接使用数字,而是要使用Index

而且,在 Swift 3.x 的时候,取字符串中的某个字符Character 还要经过 characters 这个属性,因为 String 本身不是集合类型,所以不能直接用索引来取它的值,所以要先拿到它的所有 Character,然后再遍历取值。

在 Swift 4.x 中,String 本身成为了一个集合类型,这就说明可以省略 characters 这个属性,直接对字符串遍历、切分取值了。
另外对于字符串索引,如果是遍历到最后,那么可以省略 endIndex,我想省略开头的话,startIndex 应该也可以省略。

Swfit 4.x 对字符串增加了一个新的类型,即 Substring
如果我们切分一个 String 类型的字符串,在原先 Swift 3.x 中,得到的也是一个 String 类型的字符串,但现在在 Swfit 4.x 中,我们得到的将是一个 Substring 类型的字符串。

如上图所示,假设我们有一个字符串 “Hello world”。
在 Swift 中,它有三部分组成,如上图左侧所示,即1、字符串起始位置的指针,它指向字符串在内存中的 buffer,2、字符串的长度,3、该 buffer 所属的对象,只有该对象不存在了,字符串 buffer 才能够被释放。假设现在我们得到了该字符串的子串 “world” ,该子串也有三部分组成,如上图右侧所示,关键是该子串的所属对象和原先的字符串是同一个。
这就带来一个问题,假如我们用某种方式得到了一个字符串A,它十分的长,然后取了其子串B,它很短,因为两者所属的对象是同一个,它都指向原先字符串的 buffer,所以虽然我最后取得是比较短的子串B,但是原先的字符串 buffer 却无法被释放掉,因为原先字符串A很长,所以该 buffer 会占据很大的空间。
所以现在引入了 Substring 来解决但这个问题。

如上所示 small 变量指向的其实是 Substring 对象,它不能直接赋值给 String 对象,必须要使用 String 的构造方法来构造一个 String 对象,再将其赋值给要赋值的对象,这样做了的话,原先的字符串 buffer 也就可以被释放掉了。

苹果建议我们在平常开发中尽量使用 String 对象,而不是 Substring 对象。另外 SubstringString 的用法功能很像,因为 Swift 使用类型推断机制,所以我们不必指定变量的类型,所以真正使用的时候,很多代码会和原先差别不大,例如下面这个:

虽然 keyAndValue 变量是一个 Substring 对象,但写起代码来和原先没有区别。

Swift 4.x 中对于字符串还有一个我印象比较深刻的就是支持了多行的字符串,而且它的做法和 Python 一模一样,就是使用 """ 三个冒号来圈起来,里面每行字符串的缩进要使用制表符。

独有内存访问

假设我们有这么一个方法,用来操作某一个数组,对数组的每个元素都乘2。

如果我们正确使用它,那没有任何问题

但是假如说我们在操作的时候,又对原数组进行了其他不同操作,例如移除了最后一个元素,那么最终的结果将会是不可预料得了。

Swift 4.x 增加了这方面的安全性,使得某个写操作在进行的时候,它将独占某个对象的内存访问。所以上面的操作就会报错了。

总结来说就是它现在允许同时对一块变量内存进行读,但读和写不能同时进行,多个写操作也不能同时进行。

当然上面这个错误编译器不运行就能够发现错误,但有些代码只有在运行时才能够发现错误。例如下面的代码:

现在将原先的方法放在某个类的方法中,该方法接收一个本类的对象,假如说传入的对象和自身对象不同,那没问题。但如果传入的是对象就是本身,那么上述代码的效果就和之前一样了,两个写操作同时进行,就会报错了。

其他

其他还有一些点我听得不是很明白,或者一知半解,就不拿出来说了,例如泛型我都没怎么用,所以听得也不是很懂,下次要去看看别的大神的解释再来理解了。