此次Swift5.3引入了许多新特性,不仅在语言本身,还增强了Xcode对Swift的支持及对Swift Packages的更新。本文并不打算对所有更新进行说明(实际上也完全没有必要),仅会探索一些重要且有意思的feature,如果想要深入全面了解,WWDC20也有非常多的Session来介绍这些内容,蜗牛都已经整理并贴在了文末。
Xcode12对代码缩进做了不少改善,主要有以下几个方面:
以guard
为例,在此之前缩进相当僵硬,后面条件总是无法和第一个对齐,并且包体内缩进也有问题
@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 ,以禁用新的依赖项基础结构。
Apple一直都在优化二进制文件的大小,从 Swift 4 以来,一直到 5.3 版本,从一开始的 2.3 倍,如今已经优化到 1.5 倍。
对于Swift为什么会需要这么多的binary size,官方解释是因为Swift带有一些“安全”相关的Feature,这些在生成的产物中需要有一定量的代码实现。
以官方Demo对比,使用Xcode12的编译结果就比用Xcode11下降了43%。
更新了支持的多个平台,包括:
对于非App开发的同学有用,蜗牛只是搬运工🙃。
脏内存由于不能被系统回收,能够有效减少当然是不错的,官方测试后对比了堆内存的使用情况:
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 // ... } 复制代码
Swift5.3以前,try...catch...
在多Error
类型判断时,对代码会造成一定的冗余,例如:
Swift5.3扩展了try...catch...
语法,使其catch
支持捕获不同的Error
,类似switch...case...
:
在Swift5.3中,让一个enum
遵循Comparable
协议就可支持比较,非常实用的扩展。
在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
此前的Float32
和Float64
分别是Float
和Double
的别名,并不是新的浮点格式,
而Float16
是一个新 IEEE 754 标准浮点格式。
优点:
缺点:
@main
声明入口函数在 ArgumentParser 发布了之后,大家也想要类似于 @UIApplicationMain
这样的注解去声明入口函数,并且Windows平台支持多种入口函数类型,此时 main.swift
就无法很好地满足使用。
Swift5.3支持使用@main
关键字来注解声明入口函数:
感兴趣的可以翻阅@main: Type-Based Program Entry Points。
Numerics
是一个 Apple 开源的 Swift 库,通过范型约束,提供更简单的方式,来使用所有标准库里的浮点型进行数值计算,涵盖几乎所有基本的数学函数。
作为一个Swift only的库,Numerics
是一个很好地学习如何编写和封装更优雅 Swift 代码的范例。
Swift Package Manager
发布到现在已经四年了,今年迎来两个非常重要的功能:
Emm...... Cocoapods不香吗🤭,后续有研究再补充。
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 也方便很多:
原创不易,文章有任何错误,欢迎批(feng)评(kuang)指(diao)教(wo),顺手点个赞👍,不甚感激!