WWDC20 第四弹 - Swift的新特性

3,290 阅读7分钟
此次Swift5.3引入了许多新特性,不仅在语言本身,还增强了Xcode对Swift的支持及对Swift Packages的更新。本文并不打算对所有更新进行说明(实际上也完全没有必要),仅会探索一些重要且有意思的feature,如果想要深入全面了解,WWDC20也有非常多的Session来介绍这些内容,蜗牛都已经整理并贴在了文末。

代码缩进

Xcode12对代码缩进做了不少改善,主要有以下几个方面:

  • 链式方法调用,特别是那些涉及嵌套或尾随闭包的调用
  • 当元素对齐时,不再缩进调用参数、参数列表、数组和字典文字或元组中的多行表达式
  • 多行条件语句(if,guard 和 while)

guard为例,在此之前缩进相当僵硬,后面条件总是无法和第一个对齐,并且包体内缩进也有问题

Xcode12中就舒服多了

感谢苹果爸爸满足了我的强迫症🙏🙏🙏

@escaping闭包中隐式self

在此之前,声明了@escaping的闭包需要显式使用self,但现在如果开发者能够确定不存在循环引用,Swift允许在@escaping 闭包中隐式使用self

如果用户已经在闭包的捕获列表中明确捕获了self,则现在允许在@escaping闭包中隐式使用self

class Test {
    var x = 0
    
    func execute(_ work: @escaping () -> ()) {
        work()
    }

    func method() {
        execute { [self] in
            x += 1
        }
    }
}

另外,如果self是值类型,无需明确捕获,隐式self@escaping闭包中也可使用:

struct Test {
    var x = 0

    func execute(_ work: @escaping () -> Void) {
        work()
    }

    mutating func method() {
        execute {
            x += 1
        }
    }
}

上述代码在之前就会报错:Reference to property 'x' in closure requires explicit 'self.' to make capture semantics explicit

减少增量编译

如果有这样的一个类,包含了多个类、结构体或协议,

struct A {
}

struct B {
}
...

在此之前,例如修改A,使用A或B的任何文件都将重新编译,而现在仅会对使用A的文件重新编译,这将对修改后编译速度有不错的提升。

如果编译遇到问题,可以在 Other Swift Flags 添加 -disable-type-fingerprints 以将其禁用。如果仍有问题,可以添加 -disable-fine-grained-dependencies ,以禁用新的依赖项基础结构。

Binary Size

Apple一直都在优化二进制文件的大小,从 Swift 4 以来,一直到 5.3 版本,从一开始的 2.3 倍,如今已经优化到 1.5 倍。

对于Swift为什么会需要这么多的binary size,官方解释是因为Swift带有一些“安全”相关的Feature,这些在生成的产物中需要有一定量的代码实现。

以官方Demo对比,使用Xcode12的编译结果就比用Xcode11下降了43%。

Swift Platform Support

更新了支持的多个平台,包括:

  • Apple 各OS
  • Ubuntu 16.04, 18.04, 20.04
  • CentOS 8
  • Amazon Linux 2
  • Windows(Coming soon,Swift 5.3)

对于非App开发的同学有用,蜗牛只是搬运工🙃。

Dirty Memory优化

脏内存由于不能被系统回收,能够有效减少当然是不错的,官方测试后对比了堆内存的使用情况:

Emm.....感觉并不是很明显。

代码补全

Apple做了一些小优化,支持更多场景的代码补全,比较实用的一个例子就是支持补全三元表达式:

上面还是小打小闹,Apple宣称Xcode12将在补全速度上有大幅提升。在某些场景下,其性能会强于Xcode11.5高达15倍。

以Apple的节操,15倍的优化我是不信的,之前WWDC19说App启动速度较上个版本大幅优化,但实际上个版本较上上个版本已经大幅增加了启动速度,最终优化效果并没有那么夸张。

多尾随闭包

Swift5.3以前只支持最后一个闭包参数可以使用尾随闭包:

UIView.animate(withDuration: 5, animations: {
    // ...
}) { (_) in
    // ...
}

现在Swift支持多个尾随闭包,看起来更优雅了(I like it😍):

UIView.animate(withDuration: 5) {
    // ...
} completion: { (_) in
    // ...
}

try catch支持多模式

Swift5.3以前,try...catch...在多Error类型判断时,对代码会造成一定的冗余,例如:

Swift5.3扩展了try...catch...语法,使其catch支持捕获不同的Error,类似switch...case...

比较Enum

在Swift5.3中,让一个enum遵循Comparable协议就可支持比较,非常实用的扩展。

枚举的cases支持protocol witnesses(这个要咋翻呢🧐)

在Swift5.3以前,enum必须实现protocol中的方法,而不能直接使用cases来代替。

protocol Foo {
  static var bar: Self { get }
  static func baz(arg: Int) -> Self
}

enum SomeEnum: Foo {
    static var bar: SomeEnum {
        return ._bar
    }
    
    static func baz(arg: Int) -> SomeEnum {
        return ._baz(arg: arg)
    }
    
    case _bar
    case _baz(arg: Int)
}

上述方式在使用起来很繁琐,Swift5.3对此作了优化,cases可以直接认为是protocol的实现,而无需写重复代码。

protocol Foo {
  static var bar: Self { get }
  static func baz(arg: Int) -> Self
}

enum SomeEnum: Foo {
    case bar
    case baz(arg: Int)
}

新增Float16

此前的Float32Float64分别是FloatDouble的别名,并不是新的浮点格式, 而Float16 是一个新 IEEE 754 标准浮点格式。

优点:

  • 更好的性能:由于仅占两个字节的内存,因此可以在 SIMD 寄存器或内存页中容纳两倍的数量,而在支持的硬件上,这通常会使性能提高一倍。
  • 与 C/Objective-C 里 __fp16 类型的互操性

缺点:

  • 显而易见,就是低精度和小范围

@main声明入口函数

在 ArgumentParser 发布了之后,大家也想要类似于 @UIApplicationMain 这样的注解去声明入口函数,并且Windows平台支持多种入口函数类型,此时 main.swift就无法很好地满足使用。

Swift5.3支持使用@main关键字来注解声明入口函数:

感兴趣的可以翻阅@main: Type-Based Program Entry Points

Numerics库

Numerics是一个 Apple 开源的 Swift 库,通过范型约束,提供更简单的方式,来使用所有标准库里的浮点型进行数值计算,涵盖几乎所有基本的数学函数。

作为一个Swift only的库,Numerics是一个很好地学习如何编写和封装更优雅 Swift 代码的范例。

Package Manager

Swift Package Manager 发布到现在已经四年了,今年迎来两个非常重要的功能:

  • 二进制依赖分发
  • 资源文件

Emm...... Cocoapods不香吗🤭,后续有研究再补充。

New Logger

Logger 用于收集和处理日志信息,以理解和调试应用中的意外行为。在开发中,往往会遇到难以复现的bug,或者无法在debug下直接调试。通过Logger 帮助开发者理解及处理这类疑难杂症。

使用起来也很方便:

import os

func test() {
    // subsystem 用于区分是哪个应用的日志,可以使用bundleID
    // category 用于区分是应用的哪个模块
    let logger = Logger(subsystem: "com.example.TestWWDC20", category: "testos")
    logger.log("Started a task \(taskId)")
}

再单例封装下,添加一下业务模块支持,既好用也方便统一管理。

  • 性能优化

由于日志一般量级很大,读写会影响性能,线上版本都是杜绝非必需的日志打印,例如通过print函数。为了解决这个问题,Swift和Xcode做了高度优化,只有当 log 信息真正显示的时候,才会将其转换为字符串。

  • 隐私保护

为了保护隐私,当在 log 消息中加入非数字类型的数据时,默认会标记为private,这是为了确保应用在实机运行时,不会泄漏个人信息。

当然,log 信息也支持由开发者显式标记隐私等级:

那如何对比非公开的日志呢?Logger 提供一种 equality-preserving hash 方法。在经过处理之后,不会显示真正的数据,但也能让我们判断这两条信息是否一样。

如果应用从 Xcode 中启动,即使这条 log 信息是非公开的,在 console 中也会完全显示出来,以便开发者调试。

  • 日志等级

Swift 提供了五种不同的等级:Debug,Info,Notice(默认),Error,Fault。

对于不同等级的日志,其持久化的能力也是不一样的:

相应的,各自性能也有差别:

可以看出 Debug 日志记录的性能是很快的,所以调用比较耗时的函数相对安全:

  • 格式化

Logger 支持对写入的参数进行格式化,方便阅读和对比数据:

使用了格式化后,数据就很清晰了,复制到 Numbers 也方便很多:

相关Session

原创不易,文章有任何错误,欢迎批(feng)评(kuang)指(diao)教(wo),顺手点个赞👍,不甚感激!