本文知识目录
知识点问题梳理
这里罗列了一些问题用来考察你是否已经掌握了这篇文章,如果没有建议你加入 收藏 再次阅读。
Clean Memonry 主要是指什么样的内存?Swift 是如何优化 Dirty Memory 使用率的?
Swift 5.3 中
Key Path Expressions as Functions
这个特性具有什么优点?可以和 Ruby 做一下对比?从语法糖的简洁性和使用层面来做分析。评价一下 Swift 5.3 中
Multiple trailing closures
这个新特性,可以从语言设计角度来谈一谈自己的想法。Float16
在内存和精度上面与Float32
以及Double
有什么区别?适用于什么场景?
引子
每一年的「What's new in ...」系列都会有很多提纲挈领的作用。Swift 这个逐渐完善的语言在今年也必会大放异彩,为我们开发者带来更多讨喜的新特性。在这个 Session 中,虽然无法得知 Swift 5.3 的所有进化细节,但是已经将 Swift 5.3 新特性中最重要的部分归纳到一起,让我们对新的 Swift 有一个更好的认识。
话不多说,来看正文。
Apple 开发者生态总述
随着 ABI 逐渐稳定,Swift 5 将会有越来越多的强大的 API 被逐渐开放出来。SwiftUI 就是其中的代表之一。在过去的一年中,Swift 在上层和底层的 Apple API 的开发中都付出了很多精力,来构建更加完善的 Apple 开发生态。
当然,越来越多强大的功能也离不开 Xcode 这个开发工具、Swift Package Manager 包管理器,这也是 Apple 开发生态的重要一环。与此同时,身为跨平台的 Swift 语言以及 SwiftUI 这套跨平台应用方案也是相当重要的一环。
所以在这篇 「What's new in Swift 」中,我们围绕着四个主题来一一讲述 Swift 最新两个发行版中的特性:
Swift 运行效率改善(Swift's Runtime Performance)
开发者体验(Developer Experience)
跨平台方案(Multi-platform)
语言与库(Language and Libraries)
相信看完这些将会让你提纲挈领,了解新版 Swift 最重要的信息。
Swift 运行效率改善 - Swift's Runtime Performance
Code Size
Apple 官方持续观察 Swift 项目产物中 __text
的大小。以下纵轴的单位是 Objective-C 代码生成的 __text
Section 的 Size ,横轴对应的 Swift 版本,其体现的数值是
。从 Swift 4 以来,一直到 5.3 版本,从一开始的 2.3 倍,如今已经优化到 1.5 倍。
但为什么 Swift 会需要这么多的 Code Size?官方解释到,这些差异是不可避免的,因为 Swift 带有一些“安全”相关的 Feature,这些在生成的产物中需要有一定量的代码实现,从而增加了 __text
这个 Section 的 Size。
当然不同体量的 App 会产生差异化的优化结果。例如在 Thomas Ricouard 的开源项目 MovieSwiftUI 中,使用 Xcode 12 的编译结果就比是用 11 版本的 __text
下降 43%。
Dirty Memory 使用率优化
对于一般的桌面操作系统,Clean Memory 可以认为是能够进行 Page Out 的部分。由于没有内存交换机制,对于 iOS 来说,Clean Memory 指的是能被重新创建的内存,它主要包含下面几类:
App 的二进制可执行文件
framework 中的 _DATA_CONST 段
文件映射的内存
未写入数据的内存
所有不属于 Clean Memory 的内存都是 Dirty Memory。这部分内存并不能被系统重新创建,所以 Dirty Memory 会始终占据物理内存,直到物理内存不够用之后,系统便会开始清理。
一句话来总结一下,Dirty Memory 就是指的是不能被系统回收的内存占用。那么在 Swift 5.3 中对于 Dirty Memory 有什么样的优化呢?
我们通过以下的 Mountain
Model 以及其对应的数组它们的 Memory Layout 来举例说明。假如我们现在有这么一段 Mountain
Model 的代码:
// Model 文件
@interface Mountain: NSObject
{
NSUUID *uuid;
NSString *name;
float *height;
}
@end
// 其他引用 Model 的 Code
NSArray<Mountain*> *mountains = loadMountains();
我们知道 Objective-C 中的 NSArray
存储的是对应元素的指针,而且每个对象中非基本类型也都是通过指针的方式进行保存。但是由于 Tagged Pointer 的技术我们得知,某些 NSString
由于较短未超过 8 个字节,无需使用指针存储对象,这样就达到了轻微的优化。
而在 Swift 中,我们通过对于内存的观察发现其直接存储值(其优化类似于 Tagged Pointer)的最大上限变成了 16 个字节,15 位的长度足以应对更多的场景。另外,Swift 中的数组是直接对值进行存储,避免了通过指针访问其中许多值的需要。
于是, Mountain
Model 的 Memory Layout 在 Objective-C 和 Swift 中会有如下的对比:
在这里使用了一个含有 400 个 model 对象的 demo,并测试了一下堆内存的使用情况。可以发现 Swift 5.3 已经优化到原来 Objective-C 下的 。
这些优化虽然感觉在当前的设备和系统上微不足道,但是在旧的设备和系统中会有较为明显的体验优势。
Swift 标准库下沉
这个下沉是相对于 Foundation
而言,Swift Standard Library 现在变成了 Foundation
的依赖而存在。在后续会逐渐完善 Swift 对于 Foundation 的 API,那些只能通过 C 和 Objective-C 访问到的方法也会有迁移的计划。
开发者体验
诊断策略
在新版本的 Swift Compiler 中,更新了新的诊断策略方案,优化了问题路径的提示。在 Session 中提及到的两点优化:
使用全新定位代码问题的策略;
更加精确提示以及可行性更高的提示方案;
在诊断问题时,编译器也会记录更多信息。这些优化详情可以前往 swift.org 查看更多介绍:New Diagnostic Architecture Overview。
代码补全
在这个例子中可以看到,一个空缺的字典内,编译器可以推断三元表达式的值。
另外还有一个场景就是对于动态属性的补全,例如将 KeyPath 作为方法内的传参:
除了更强大的补全能力以外,在速度上 Xcode 12 也有大幅度的提高。在某些场景下(请注意这句话),其性能会强于 Xcode 11.5 高达 15 倍!
代码缩进
新版 Xcode 中的代码缩进也得到了显著的改善,这些改善已经合入了开源的 SourceKit 中。对于缩进的修改有以下几个方面:
链式方法调用;
调用参数;
元祖元素;
跨多行的集合元素;
多行条件语句(
if
,guard
和while
);
这里举一个 MovieSwiftUI
项目中的例子,来体现新版的链式调用改善后的缩进风格:
调试
首先我们知道 Swift 是通过 clang 的 Modules 方式 从 Objective-C 代码中导入 API ,来解析相关类型和变量信息。在这个过程中,LLDB 需要导入当前调试上下文中所有可见的 Swift 模块和 clang Modules。
虽然这些 Module 文件有大量的类型描述信息,但由于 LLDB 会带有整个应用程序以及全部动态库的全环境信息,所以在导入 clang Modules 时会失败,原因往往是“编译期没有实现”。
为了应对这种情况,新版本的 LLDB 会从 DWARF
调试信息中导入 C 和 Objective-C 的类型到 Swift 中作为兜底方案。这样也就提高了 Xcode 中 Variable View 和 Expression Evaluator 这些与实例对象相关的调试稳定性。
Swift 平台兼容性 - Swift Platform Support
Swift 在官方的发行版也已经支持多个平台,其中有:
Apple 各个系统;
Ubuntu 16.04, 18.04, 20.04;
CentOS 8;
Amazon Linux 2;
Windows(Coming soon,Swift 5.3)
由于兼容了多个操作系统环境,使得 Swift 将会在更多有趣的场景上投入使用。其中 AWS Lambda 服务就是其中之一。
Serverless Functions(国内通常说是 FasS 功能即服务,或者说是云函数)服务是一种快速落地服务端功能的十分便捷的方式。对于移动端开发,我们可以不用关注很多运维及后端知识。而现在,使用开源的 Swift AWS Runtime 服务来做 Serverless 开发也是个十分简单的方式。
我们通过下面这三行简单的代码,并依托于 Amazon 的 AWS Lambda 服务就可以快速上线一个服务:
语言 - Language
Swift 5.3 又更新了一大批的新特性:
SE-0249: Key Path Expressions as Functions
SE-0253: Callable values of user-defined nominal types
SE-0263: Add a String Initializer with Access to Uninitialized Storage
SE-0264: Standard Library Preview Package
SE-0266: Synthesized comparable conformance for enum types
SE-0267: Where clauses on contextually generic declarations
SE-0268: Refined didSet semantics
SE-0269: Increase availability of implicit self in
@escaping
closures when reference cycles are unlikely to occurSE-0270: Add Collection Operations on Noncontiguous Elements
SE-0276: Multi-Pattern Catch Clauses
SE-0277: Float16
SE-0279: Multiple trailing closures
SE-0280: Enum cases as protocol witnesses
SE-0281:
@main
: Type-Based Program Entry Points
下面我们来分析一些比较有代表性的 Feature,来细说一下:
SE-0279: 多尾部闭包
在之前的 Swift 版本,尾随闭包只允许最后一个参数,新的版本则允许多个尾部闭包,以一个 UIView
的动画为例:
UIView.animate(withDuration: 0.3) {
self.view.alpha = 0
} completion: { _ in
self.view.removeFromSuperview()
}
这种多个尾随闭包的语法也非常适合 DSL。这里举了一个 SwiftUI 的例子:
使用多尾部闭包的语法糖,让链式的写法更加的自然流畅。
SE-0249: Key Path 函数化
在 Swift 4.1 引入了 KeyPath。在新的版本中则可以使用其来作为函数,前面的代码补全提示也有类似例子。
这个例子中,我们使用 Key Path 来访问每一个元素的 shoeSize
属性,从而代替了一个繁琐的闭包。
这个语法糖其实也特别像 Ruby 当中的 to_proc & symbol 的语法,即我们可以:
# Ruby
contacts.chunked(&:showSize)
// Swift
contacts.chunked(by: \.shoeSize)
你更喜欢哪个语言的表达呢?
SE-0281: @main
程序入口
在之前版本的 Swift,使用 @UIApplicationMain
来告诉编译器隐式生成 main.swift
来运行应用程序。在 Swift 5.3 中,Apple 对这个功能做了延伸。
例如,我们可以使用 @main
这个修饰关键字,来描述命令行的入口点:
SE-0269: 闭包中隐式 self
一般在闭包中,我们要显式的声明 self
,并且访问属性时也要通过 self dot 的方式,而新的版本则在闭包中对结构体或枚举增加隐式的 self
,以下是 SwiftUI 中的一个例子:
SE-0276: 多模式捕获
在之前的 Swift 版本中,try...catch...
语句在生产环境中,实际的代码会造成一定的冗余。例如在多 Error
类型判断时,往往需要在 catch
子句中增加 switch...case...
语句来进一步判断:
在 Swift 5.3 中,Apple 扩展了 try...catch...
语法,使其子句具有 switch
语句的判断作用,从而避免了嵌套语句的出现。
Enum Enhancements
1. 比较
在 Swift 5.3 中,让一个 enum
遵循 Comparable
就可支持比较。
2. 枚举的 cases
可以作为 protocol witnesses
在旧版的 Swift 中,假设我们有一个场景:
// DecodingError 协议
protocol DecodingError {
static var fileCorrupted: Self { get }
static func keyNotFound(_ key: String) -> Self
}
// 我们在 JSONDecodingError 中需要有两个 cases
// fileCorrupted 和 keyNotFound,并且是遵循 DecodingError
// 但由于这两个协议必须写成方法的形式,所以要重复写一遍
// 即使在调用语法表达上是一样的
enum JSONDecodingError: DecodingError {
case _fileCorrupted
case _keyNotFound(_ key: String)
static var fileCorrupted: Self { return ._fileCorrupted }
static func keyNotFound(_ key: String) -> Self { return ._keyNotFound(key) }
}
针对这个场景,Swift 5.3 推出了 SE-0280: Enum cases as protocol witnesses 这个 Feature,现在我们的代码只需要如此简化即可完成上述场景:
Embedded DSL Enhancements
Swift 5.3 继续增强了(SwiftUI 中的) DSL 的语法,目前已经支持了部分复杂的条件语句结构,例如 switch...case...
、if let ...
等等。
另外需要给大家介绍的一点是,在 Swift 5.3 中,builder attribute
不用再显式声明了。
库 - Libraries
SDK - Float16
Float16 是 Swift 5.3 的新 IEEE 754 标准浮点格式。Float16 仅占用两个字节的内存,而单精度浮点数占用四个字节。由于它只有一半大小,因此可以在 SIMD 寄存器或内存页中容纳两倍的数量,而在支持的硬件上,这通常会使性能提高一倍。
但是请注意,作为较小的数据类型,它的精度和范围也更加有限。
SDK - Apple Archive
Apple Archive
是 Apple 新推出的无损归档压缩库。AppleArchive
提供了快速压缩的功能并具有以下特点:
多核多线程处理,速度更快;
能够携带文件所有的属性,并在适当时机使用 Apple File System(APFS)功能,例如:filesystem compression、full clones 或者 sparse files;
灵活的编码格式,支持纠错、归档、编写摘要等功能;
API 支持内存中的存档处理,流访问;
值得注意的一点是,在官方的 Session 中提及到
ArchiveByteStream.withFileStream()
这个构造方法使用了 Apple 的一个新库Swift System
。
Swift System
为 Apple Archive 等系统底层 API 更加现代、易用的系统调用接口 API 以及更加通用的类型。但是在经过译者一番 Google 之后,还是没有找到这个库的资料,后面可以关注一下。
SDK - OSLog
在 Swift 5.3 中,Apple 通过编译器优化功能,使 OSLog
更快,并增加了对字符串插值和格式设置功能。也许这个版本的 OSLog
是你替换 print
方法的最好时机。
当然,如果你想了解更多关于 OSLog
的信息,可以看这个 Session 10168 - Explore logging in Swift。
Packages - Swift Numerics
Swift Numerics
是面向数学/科学计算的库,涵盖几乎所有基本的数学函数(例如正弦和对数),另外还兼容复数的计算。
这个库弥补了 Swift 在数学领域上的不足。如果你对这个 Package 感兴趣,可以在官网查看 Swift Numerics。
Packages - Swift ArgumentParser
Swift ArgumentParser
也是一个开源 Swift 库,用于命令行参数解析。
区别于其他 CLI Argument Parser ,官方的 Swift ArgumentParser
会遵循以下设计原则进行开发:
对于命令行的提示和帮助十分友好,并提供一个编写的最佳实践;
支持简单的一次性脚本命令,也支持复杂的多子命令嵌套,并提供良好的帮助信息;
干掉了解析参数时的问题,通过 Swift 封装降低出错的概率。
当然如果你对这个 Package 感兴趣,可以到官网中查看 Announcing ArgumentParser。
Packages - Swift StandardLibraryPreview
这个 Package 用来提供 Swift 标准库的预发布版本,你可以通过 Swift Package Manager 引入并预先体验。
这个是官方已经宣布的 Swift Evolution 流程增强之一。这个 Preview 版本功能上线后,在正式版本上线前可以结合社区的反馈来进行 Feature 的复审,从而决定新 Feature 的去留。
当然如果你想了解更多关于 StandardLibraryPreview 这个 Package 的相关信息,可以访问官网 Standard Library Preview Package。
总结
今年已经是 Apple 在 Swift 端发力的第 6 年。从目前 iOS 开发生态来看,Swift 已经成为我们 iOS 开发者的大势所趋,学习是在所难免的。依靠 Swift 的生态,其实我们还可以将技术栈横向迁移,去做更多有趣的事情。
相关阅读 - 如今 Swift 在各个大厂落地情况如何了?来看看这篇文章「一次关于 Swift 在 iOS 生态圈里的现状调研」。