WWDC17 - What's New in Swift

作者: Hesse_Huang | 来源:发表于2017-06-13 22:09 被阅读874次

    官方视频地址: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 中手动开启,效果究竟如何有待商榷。
    New Build System
    • 预编译的桥接头文件
      在 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 中,烦人的索引过程被挪到了构建时才执行。同样开销非常小。每次构建将更新索引,以实现更精准的索引结果。终于可以跟它说滚蛋了:
    万恶的Indexing
    • 可预测的性能(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存在容器相似的优化效果。

    COW优化后的存在容器
    • 更小的二进制包大小(Smaller Binaries)
      减小二进制包大小也就意味着用户的 App 更小。Swift 4 中有这几个方面的优化让我们的 App 瘦身:

      1. 移除未使用的(协议)实现代码。Swift 4 的编译器可分析出哪些(协议)实现代码是没有被使用的,从而在打包时移除掉这些代码。然而这个分析+移除的策略还不是那么完美,需要改动一下 Swift 的语法,也就是下面一项。

      2. 显式写 @objc 关键字以避免自动生成 Objective-C thunk 函数。原来啊,Swift 3 编译时会帮 NSObject 的子类构建一份Objective-C 版本的 thunk 函数以供其 runtime 时调用。这些 thunk 函数虽 然最终还是调用 Swift 的实现,但也占二进制大小,还让(1)中的优化无法实现。因此在 Swift 4 + Objective-C 的混编项目中,需要显式写 @objc 关键字,暴露需要用到的 Swift 方法。如果有多个方法要暴露给 Objective-C,建议你把它们放入 @objc extension 中。

    Swift3会自动加入@objc关键字 迁移代码的最后一步
    • 剥除 Swift 符号
      Swift 团队为 libswiftCore 等核心库减小了 symbol 的大小占用,方法是使用更简洁的命名和剥除 Swift 符号。

      至于
      剥除符号(Symbol Stripping)
      的原因,请允许我翻译一下视频中的原话:

    "静态链接器和动态链接器分别用自己的字典树来快速查找符号,也就是说 Swift 的 symbols 放在符号表中是基本上没用的。"

    因此在 Xcode 9 中,Strip Swift Symbols 默认开启以优化我们工程中自己的 Swift 代码;而对于系统库的 Swift 代码,则在 App Thinning 中才进行符号剥离,同样有一个Strip Swift Symbols的选项可供勾选。

    优化后的系统库大小对比 工程中开启 Symbol Stripping 导出时的 Symbol Stripping 选项

    四、标准库

    这一小节主要讲了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...,表示从strstartIndex到其endIndex

      再举个粟子:

    String 的 zipping
    • 截取部分字符串的 API 变了,返回的类型也变了。

      let str = "one,two,three"
      
      // Swift 3
      str.components(separatedBy: ",") // 返回 Array<String>
      
      // Swift 4
      str.split(separator: ",") // 返回新类型 Array<Substring>
      

      此举有利有弊。

      利:不再执行深拷贝,减少内存分配和性能开销;

      弊:原来的字符串大哥有可能被其切片后的小弟保持强引用,导致大哥释放不了。

      因此,要在适当的时候显式将Substring转成String

      String与Substring共享缓存.png
    • 使用一对三双引(""")来包装多行字符串字面量。如下图所示,注意缩进:(开源的力量!👏)
    三双引
    • 新的泛型特性: protocolassociatedtype可以使用where子句,以实现对不同associatedtype之间的约束。视频给出了 Swift 标准库中利用这个新特性优化了 SequenceCollection的案例。

    • 新的泛型特性:泛型下标(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 = [],那就会报错:排他性存取!

    非排他时触发了COW

    排他性存取的检测分为编译时运行时两种。编译时检测可直接报错;运行时检测则会抛出异常,如下图。

    运行时报错

    有点像多线程的问题对吧?至此,我们只是在单线程下看这个排他性存取,而如果在多线程下触发运行时的排他性存取,那就要通过 Thread Sanitizer 处理了。

    当前,在 Swift 3.2 下的非排他性存取只会报 warning,但在未来的 Xcode 中会升级为 error。

    内存的排他性存取保证了安全性的同时,也为从中优化了标准库的设计。

    在工程设置中可以调整 Exclusive Access to Memory 的策略。

    WX20170613-211406@2x.png

    总结

    Swift 4 在 Swift 3 的基础上升级,没有像去年那样巨大的迁移成本。升级后的 Swift 更快速、更安全,配合着 Xcode 9,生成的 App 体积将更小。

    最后

    此文粗略,或有错漏,烦请指明,当天修正!

    终于写完了!全程无字幕听着画重点,哈哈!🐶
    Let's Swift!🎉

    相关文章

      网友评论

      本文标题:WWDC17 - What's New in Swift

      本文链接:https://www.haomeiwen.com/subject/sjqqqxtx.html