前言
视频地址:https://developer.apple.com/videos/play/wwdc2019/402/
二进制库稳定
ABI 稳定
Swift 5 带来了 ABI 的稳定。但是用 Swift 写的二进制框架并没有稳定。
先说一下什么是ABI(Application Binary Interface),打个比方,代码中调用一个函数,需要传入几个参数,调用结束需要返回结果,编译器如何将参数传给被调用者,以及如何将结果返回给调用方,就是 ABI 涉及的内容。ABI 不稳定意味则,如何传参数和返回参数还没有最终确定(当然这只是举例,实际的编译器实现很复杂,ABI 涉及很多方面)。ABI 不稳定导致的结果就是,使用 Swift 5 以前的版本编写 iOS app,Xcode 需要同时将 ABI 相关的 Swift 核心库编译到你的 app 中,直观的感受就是你的 ipa 包变大了。
iOS 12.2系统内置 Swift 核心库所以 Swift5 编写的 app 不需要内置 Swift 核心库了,但是 Xcode 在打包的时候仍然会将 Swift5 核心库集成到 ipa 里面,这样 Swift5 写的 app 就能运行在 iOS12.2 之前系统。只是对于 iOS 12.2 及以后的系统安装该 app 的时候,系统不需要下载 app 中 Swift 的核心库了,减少了下载体积和安装大小。(同时 app 启动也变快了,因为系统 dyld3 缓存了对应的 Swift 核心库,官方说能带来 5% 的启动速度提升)
ABI 稳定之后就是模块稳定了。
模块稳定
模块不稳定指的是,用Swift5.1 以前编写的模块,如果想要提供给第三方的话,需要把这个模块的代码也提供第三方,这样他们才能使用这个模块。当然这对于某些模块的开发者来说是不可接受的(毕竟是把模块的源代码给别人了)。模块库稳定指的是,用 Swift5.1,库开发者不需要提供源代码给第三方了,只需要提供一个类似头文件的东西给第三方,第三方就可以使用了。
顺便简单说一下Swift 是如何使用模块。如下图所示:
Swift5及以前
可以知道Swift5.1 以前,在 Foo.swift 中 import MyFramework 的话,Swift 会自动在 Foo.swift 中导入 MyFramework 的“头文件”亦即 swiftmodule 文件,这个文件是编译器在编译 MyFramework 的时候生成的。如此一来,就可以在 Foo.swift 中任意使用 MyFramework 中的类、结构体、函数等了,比如图中的 doSomething 方法。
Swift5.1 也差不多,如下图:
只是将swiftmodule 文件改成了 swiftinterface 文件。当然 swiftinterface 对于库开发者来说应该是可以编辑的。库开发者需要将这个文件和二进制文件提供给第三方。
ABI 和模块的稳定意味者二进制库的稳定。
Swift5.1 的优化
[if !supportLists]1. [endif]编译出来的二进制更小了,通常是10% 的减少,如果开启 ‘Optimize for Size‘ 就是 15% 的减少。
[if !supportLists]2. [endif]Swift 和 ObjC 的桥接更快了
[if !supportLists]1. [endif]NSString 和 String 桥接有15x 的提升
[if !supportLists]2. [endif]NSDictionary 和 Dictionary 有 1.6x 的提升
[if !supportLists]3. [endif]大小比较小的String 的优化
这些对String 和 Dictionary 的优化使得 SwiftNIO 请求处理速度提升了 20%。
SwiftNIO 是 Swift 官方出的跨平台异步事件驱动的网络应用框架
Swift 开源相关
[if !supportLists]1. [endif]Swift 官方 docker 镜像
[if !supportLists]2. [endif]Swift 的 SourcKit (用于代码补全、高亮、重构以及符号跳转到定义等编写 Swift 代码相关的编辑器功能)的压力测试(为什么压力测试,用过 Xcode 的都知道 Xcode 的 Swift 编辑器有多烂
[if !supportLists]3. [endif]LSP(Language Server Protocol)
LSP 的作用如下所示,简单的说就是代码编辑器有很多比如 Xcode 的 Source Editor、VSCode、Vim 等等,每个编辑器都要支持各种类型代码(比如 Java、Python、Swift 等)编辑相关的操作(高亮、补全等)。
由于SourceKit 定义的内容被 Xcode 使用,不够通用,没法直接给 VSCode 等第三方编辑器使用,所以需要使用 LSP 协议来做桥接(计算机领域有句名言,所有复杂问题都可以引入另一个中间层来解决)。
Swift 新特性
所有的新特性都可以在https://apple.github.io/swift-evolution/中找到。
这里介绍比较重要的一些内容。
单行表达式隐式返回
这个很简单。在以前需要写这种代码:
// Implicit return from single expressions// Swift Evolution: SE-0255
struct Rectangle {
var width = 0.0, height = 0.0
var area: Double { return width * height }.}.
现在Swift5.1 可以这样写了
// Implicit return from single expressions// Swift Evolution: SE-0255struct Rectangle {
var width = 0.0, height = 0.0
var area: Double { width * height }.}.
结构体初始化
以前需要这样初始化结构体
// Synthesized default values for the memberwise initializer// Proposal and implementation by an open source contributor Alejandro Alonso// Swift Evolution: SE-0242struct Dog {
var name = "Generic dog name"
var age = 0}let boltNewborn = Dog()let daisyNewborn = Dog(name: "Daisy", age: 0) let benjiNewborn = Dog(name: "Benji") // 编译错误❌
现在最后一句也能编译通过了。
字符串插值
字符串插值指的是:
let quantity = 10
label.text = "You have \(quantity) apples"
Swift 5.1 以前,不能这样写
let quantity = 10
label.text = NSLocalizedString(
"You have \(quantity) apples",
comment: "Number of apples") // 编译错误❌
需要改成这样子:
let quantity = 10let formatString = NSLocalizedString(
"You have %lld apples",
comment: "Number of apples")
label.text = String(format: formatString, quantity)
Swift5.1 可以这样写了:
let quantity = 10
return Text(
"You have \(quantity) apples"
,
comment: "Number of apples"
)
其中Text 来自 SwiftUI 框架,定义如下:
// In SwiftUI.frameworkpublic struct Text {
public init(
_ key: LocalizedStringKey,
tableName: String? = nil,
bundle: Bundle? = nil,
comment: StaticString? = nil
)}
Swift 会将上述代码转换成这样:
let quantity = 10return Text(
"You have \(quantity) apples",
comment: "Number of apples")
// Generated by the Swift compilervar builder = LocalizedStringKey.StringInterpolation(
literalCapacity: 16, interpolationCount: 1).
builder.appendLiteral("You have ")
builder.appendInterpolation(quantity)
builder.appendLiteral(" apples")LocalizedStringKey(stringInterpolation: builder)
这些得益于ExpressibleByStringInterpolation 这个新的协议
返回值类型抽象化
为什么要抽象化返回值类型呢?因为不想暴露实现细节,但是在Swift5.1 以前某些情况下有问题。
如下类型:
// Shapes Example
protocol Shape { /* ... */ }struct Square: Shape { /* ... */ }struct Circle: Shape { /* ... */ }struct Oval: Shape { /* ... */ }struct Union
下面这种情况还好:
// API returns different types conforming to the same protocol// Use Protocolstruct FaceShape {
…
var shape: Shape {
switch faceType {
case .round:
return Circle()
case .square:
return Square()
case .diamond:
return Transformed(Square(), by: .fortyFiveDegrees))
default:
return Oval()
}
}}
这种情况:
// API returns the same type but is leaking implementation detailsstruct EightPointedStar {
…
var shape: Union
return Union(Square(), Transformed(Square(), by: .fortyFiveDegrees))
}}
就不能写成下面这样了:
// API returns the same type but is leaking implementation details// Protocol type?struct EightPointedStar {
…
var shape: Shape {
return Union(Square(), Transformed(Square(), by: .fortyFiveDegrees))
}}
为什么呢?因为缺少了类型信息。而且Swift 的泛型系统支持的不够好,同时阻碍了 Swift 编译器的优化。
在Swift5.1 中可以这样写了(引入了 some 关键字)
// API returns the same type but is leaking implementation details// Use Opaque Result Typesstruct EightPointedStar {
…
var shape: some Shape {
return Union(Square(), Transformed(Square(), by: .fortyFiveDegrees))
}}
但是还是不能这样写:
// Opaque Result Types// Compiler enforces that the same type is returned from the implementation
struct EightPointedStar {
…
var shape: some Shape {
if symmetrical {
return Union(Square(), Transformed(Square(), by: .fortyFiveDegrees))
} else {
return Transformed(Square(), by: .twentyDegrees)
}
}}
属性包装
很重要!!!
直接上代码,在以前,会有这样的代码:
static var usesTouchID: Bool {
get {
return UserDefaults.standard.bool(forKey: "USES_TOUCH_ID")
}
set {
UserDefaults.standard.set(newValue, forKey: "USES_TOUCH_ID")
}}
static var isLoggedIn: Bool {
get {
return UserDefaults.standard.bool(forKey: "LOGGED_IN")
}
set {
UserDefaults.standard.set(newValue, forKey: "LOGGED_IN")
}}
现在,使用Swift5.1 可以这样了:
// Using UserDefault property wrapper to declare and access properties@UserDefault("USES_TOUCH_ID", defaultValue: false)static var usesTouchID: Bool@UserDefault("LOGGED_IN", defaultValue: false)static var isLoggedIn: Bool
if !isLoggedIn && usesTouchID {
!authenticateWithTouchID()}
问题来了:@UserDefault 哪里来的?UserDefault 其实是一个泛型结构体,如下所示:
// The purpose of property wrappers is to wrap a property, specify its access patterns
@propertyWrapperstruct UserDefault
let key: String
let defaultValue: T
init(_ key: String, defaultValue: T) {
…
UserDefaults.standard.register(defaults: [key: defaultValue])
}
var value: T {
get {
return UserDefaults.standard.object(forKey: key) as? T ?? defaultValue
}
set {
UserDefaults.standard.set(newValue, forKey: key)
}
}}
定义以上内容之后,我们就可以使用@UserDefault 这种方式了。
熟悉Java 的人会说这是注解,熟悉 Python 或者 JavaScript 的会说这是装饰器,不管如何,两者有异曲同工的效果!
DSL(Domain Specific Languages,领域特定语言)
视频中Swift 开发者介绍了 Swift 就有了一项新的能力: 很容易就能支持 DSL!
DSL 是一种描述语言,描述一种特定的内容,比如界面的结构等等。这种描述语言需要编程语言作为载体(或者其他,但是编程语言是其中最好的一个载体)。对 DSL 不熟悉的可以查看文末详细链接。
DSL 简单的比喻就是,将原本字符串描述的内容用编程语言描述出来。比如 XML 这种格式的字符串可以描述很多,比如界面和数据结构等,但是为什么一定要编程语言描述出来呢?
[if !supportLists]1. [endif]因为编程语言有编译器的帮助可以帮助你写出合法、有效的描述,并且支持编译优化,
[if !supportLists]2. [endif]编程语言可以被执行,所以if-else 语句,while、for 语句,等等各种表达式以及对应编程语言的所有特性都能使用,帮助你构建灵活的描述
[if !supportLists]3. [endif]编辑器在编译器的帮助下可以支持代码高亮、补全等等,加快你的开发。
[if !supportLists]4. [endif]编程语言是严谨的精确的,而且开发人员喜欢写代码!而不是写各类配置或者字符串描述,想象一下,你写字会有错别字这种情况。
接下来介绍Swift 具体是如何支持 DSL 的。
我们现在可以这样子用Swift5.1 写代码描述一个 HTML 文档了:
html {
head {
title("\(name)'s WWDC19 Blog")
}
body {
h2 { "Welcome to \(name)'s WWDC19 Blog!" }
br()
"Check out the talk schedule and latest news from "
a {
"the source"
}.href("https://developer.apple.com/wwdc19/")
if !loggedIn {
button {
"Log in for comments"
}
.onclick("triggerLogin()")
}
}..
}..
如何实现的呢?
如下所示:
// Functions that construct the HTML objectspublic func html(@HTMLBuilder content: () -> HTML) -> HTML { … }public func head(@HTMLBuilder content: () -> HTML) -> HTML { … }public func body(@HTMLBuilder content: () -> HTML) -> HTML { … }
可见,这些函数定义使用了上一节说的属性包装器,也就是HTMLBuilder,有了这个包装器,下面的代码:
head {
meta().charset("UTF-8")
if cond {
title("Title 1")
} else {
title("Title 2")
}
}
在运行时候的效果类型下面这样子:
head {
let a: HTML = meta().charset("UTF-8")
let d: HTML
if cond {
let b: HTML = title("Title 1")
d = HTMLBuilder.buildEither(first: b)
} else {
let c: HTML = title("Title 2")
d = HTMLBuilder.buildEither(second: c)
}
return HTMLBuilder.buildBlock(a, d)}
嗯,如果关注WWDC19 的话你会发现,iOS13 引入的新的 UI 编程框架 SwiftUI.framework 这个框架就是像上面这样被实现的!!
不过可惜的是Swift 支持的@_functionBuilder在会上并没有透露出来,这个东西也是我在网上找到的,可以查看本文引用中的Swift functionBuilder
引用
[if !supportLists]1. [endif]Apple 介绍 dyld3
[if !supportLists]2. [endif]Swift 官方博客关于 ABI 稳定的介绍
[if !supportLists]3. [endif]Swift UTF8 String
[if !supportLists]4. [endif]SwiftNIO
[if !supportLists]5. [endif]Swift SourcKit 压力测试
[if !supportLists]6. [endif]LSP
[if !supportLists]7. [endif]sourcekit-lsp
[if !supportLists]8. [endif]swift-evolution/
[if !supportLists]9. [endif]ExpressibleByStringInterpolation
[if !supportLists]10. [endif]Opaque Result Types
[if !supportLists]11. [endif]New Design for String Interpolation
[if !supportLists]12. [endif]SIMD — A Better API for Vector Programming
[if !supportLists]13. [endif]Property Wrapper Types
[if !supportLists]14. [endif]Domain Specific Languages
[if !supportLists]15. [endif]Swift 中类似 Java 的注解:Attribute
[if !supportLists]16. [endif]Swift functionBuilder