Swift ABI 稳定对我们到底意味着什么

Swift 社区最近最重大的新闻应该就是 ABI 稳定了。这个话题虽然已经讨论了有一阵子了,但随着 Xcode 10.2 beta 的迭代和 Swift 5 的 release 被提上日程,最终 Swift ABI 稳定能做到什么程度,我们开发者能做些什么,需要做些什么,就变成了一个重要的话题。Apple 在这个月接连发布了 ABI Stability and MoreEvolving Swift On Apple Platforms After ABI Stability 两篇文章来阐述 Swift 5 发布以后 ABI 相关的内容所带来的改变。虽然原文不是很长,但是有些地方上下文没有说太清楚,可能不太容易理解。本文希望对这个话题以问答的形式进行一些总结,让大家能更明白将要发生的事情。

我是一个 app 开发者,Swift 5 发布以后会怎么样?

简单说,安装 Xcode 10.2,然后正常迁移就可以了,和以往 Swift 3 到 Swift 4 需要做的事情差不多。单论 Swift 5 这个版本,不会对你的开发造成什么影响,直到下一个版本 (比如 Swift 5.1) 之前,你几乎不需要关心 ABI 稳定这件事。关于下个 Swift 版本,我们稍后会提到这件事情。

我还是想知道什么是 ABI 稳定?

就是 binary 接口稳定,也就是在运行的时候只要是用 Swift 5 (或以上) 的编译器编译出来的 binary,就可以跑在任意的 Swift 5 (或以上) 的 runtime 上。这样,我们就不需要像以往那样在 app 里放一个 Swift runtime 了,Apple 会把它弄到 iOS 和 macOS 系统里。

所以说 app 尺寸会变小?

是的,但是这是 Apple 通过 App Thinning 帮我们完成的,不需要你操心。在提交 app 时,Apple 将会按照 iOS 系统创建不同的下载包。对于 iOS 12.2 的系统,因为它们预装了 Swift 5 的 runtime,所以不再需要 Swift 的库,它们会被从 app bundle 中删掉。对于 iOS 12.2 以下的系统,外甥打灯笼,照旧。

一个新创建的空 app,针对 iOS 12.2 打包出来压缩后的下载大小是 26KB而对 iOS 12.0 则是 2.4MB。如果你使用了很多标准库里的东西,那这个差距会更大 (因为没有用到的标准库的符号会被 strip 掉),对于一个比较有规模的 app 来说,一般可以减小 10M 左右的体积。

还有什么其他好处么?

因为系统集成了 Swift,所以大家都用同一个 Swift 了,app 启动的时候也就不需要额外加载 Swift,所以在新系统上会更快更省内存。当然啦,只是针对新系统。

另外,对于 Apple 的工程师来说,他们终于能在系统的框架里使用 Swift 了。这样一来,很多东西就不必通过 Objective-C wrap 一遍,这会让代码运行效率提高很多。虽然在 iOS 12.2 中应该还没有 Swift 编写的框架,但是我们也许能在不久的将来看到 Swift 被 Apple 自己所使用。等今年 WWDC 的消息吧。

我还想用一段时间的 Xcode 10.1,不太想这么快升级

Xcode 10.1 里的是 Swift 4.2 的编译器,出来的 binary 不是 ABI 稳定的,而且必定打包了 Swift runtime。新的系统发现 app 包中有 Swift runtime 后,就会选择不去使用系统本身的 Swift runtime。这种情况下一切保持和现在不变。旧版本的 Xcode 只有旧版本的 iOS SDK,所以自然你也没有办法用到新系统的 Swift 写的框架,系统肯定不需要在同一个进程中跑两个 Swift runtime。

简单说,你还可以一直使用 Xcode 10.1 直到 Apple 不再接受它打包的 app。不过这样的话,你不能使用新版本 Swift 的任何特性,也不能从 ABI 稳定中获得任何好处。

我升级了 Xcode 10.2,但是还想用 Swift 4 的兼容模式,会怎么样?

首先你需要弄清楚 Swift 的编译器版本语言兼容版本的区别:

编译器版本 语言兼容版本 对应的 Xcode 版本
Swift 5.0 Swift 5.0, 4.2, 4.0 Xcode 10.2
Swift 4.2 Swift 4.2, 4.0, 3.0 Xcode 10.0, Xcode 10.1
更多历史版本 …    

同一个 Xcode 版本默认使用的编译器版本只有一个 (在你不更换 toolchain 的前提下),当我们在说到“使用 Xcode10.2 的 Swift 4 兼容模式”时,我们其实指的是,使用 Xcode 10.2 搭载的 Swift 5.0 版本的编译器,它提供了 4.2 的语法兼容,可以让我们不加修改地编译 Swift 4.2 的代码。即使你在 Xcode 10.2 中选择语言为 Swift 4,你所得到的二进制依然是 ABI 稳定的。ABI 和你的语言是 Swift 4 还是 Swift 5 无关,只和你的编译器版本,或者说 Xcode 版本有关。

多提一句,即使你选择了 Swift 4 的语言兼容,只要编译器版本 (当然,以及对应的标准库版本) 是 5.0 以上,你依然可以使用 Swift 5 的语法特性 (比如新增加的类型等)。

看起来 ABI 稳定很美好,那么代价呢?

Good question! 我们在第一个问题里就提到过,一切都会很美好,直到下一个版本。因为 Swift runtime 现在被放到 iOS 系统里了,所以想要升级就没那么容易了。

在 ABI 稳定之前,Swift runtime 是作为开发工具的一部分,被作为库打包到 app 中的。这样一来,在开发时,我们可以随意使用新版本 Swift 的类型或特性,因为它们的版本是开发者自己决定的。不过,当 ABI 稳定后,Swift runtime 变为了用户系统的一部分,它从开发工具,变为了运行的环境,不再由我们开发者唯一决定。比如说,对应 iOS 13 的 Swift 6 的标准库中添加了某个类型 A,但是在 iOS 12.2 这个只搭载了 Swift 5 的系统中,并没有这个类型。这意味着我们需要在使用 Swift 的时候考虑设备兼容的问题:如果你需要兼容那些搭载了旧版本 Swift 的系统,那你将无法在代码里使用新版本的 Swift runtime 特性。

这和我们一直以来适配新系统的 API 时候的情况差不多,在 Swift 5 以后,我们需要等到 deploy target 升级到对应的版本,才能开始使用对应的 Swift 特性。这意味着,我们可能会需要写一些这样的兼容代码:

// 假如 Swift 6.0 是 iOS 13.0 的 Swift 版本
if #available(iOS 13.0, *) {
    // Swift 6.0 标准库中存在 A
    let a = A()
} else {
    // 不存在 A 时的处理
}

对于“新添加的某个类型”这种程度的兼容,我们可以用上面的方式处理。但是对于更靠近语言层面的一些东西 (比如现在已有的 Codable 这样的特性),恐怕适配起来就没有那么简单了。在未来,Deployment target 可能会和 Swift 语言版本挂钩,新的语言特性出现后,我们可能需要等待一段时间才能实际用上。而除了那些纯编译期间的内容外,任何与 Swift runtime 有关的特性,都会要遵守这个规则。

可以像现在一样打包新版本的 Swift runtime 到 app 里,然后指定用打包的 Swift 版本么

不能,对于包含有 Swift runtime 的系统,如果运行的 binary 是 ABI 稳定的,那么就必须使用系统提供的 Swift。这里的主要原因是,Apple 想要保留使用 Swift 来实现系统框架的可能性:

  1. 如果允许两个 Swift runtime (系统自带,以及 app 打包的),那么这两个运行时将无法互相访问,app 也无法与系统的 Swift 框架或者第三方的 ABI 稳定的框架进行交互。
  2. 如果允许完全替换 Swift runtime,系统的 Swift 框架将执行用户提供的 Swift 标准库中的代码,这将造成重大的安全隐患。

有任何可能性让我能无视系统版本,去使用 Swift 的新特性么

有,但是相对麻烦,很大程度上也依赖 Apple 是否愿意支持。如果你还记得 iOS 5.0 引入 ARC 时,Apple 为了让 iOS 4.3 和之前的系统也能使用 ARC 的代码,在 deployment target 选到 iOS 4.3 或之前时,会用 static link 的方式打包一个叫做 libarclite 的库,其中包含了 ARC 所需要的一些 runtime 方法。对于 ABI 稳定后的 Swift,也许可以采用类似做法,来提供兼容。

这种做法在感觉上和 Android 的 Support Library Packages 的方式类似,但是 Apple 似乎不是很倾向于提供这样的官方支持。所以之后要看有没有机会依靠社区力量来提供 Swift 的兼容支持了。

不能第一时间用上新的语言特性,必然会打击大家进行适配和使用新特性的积极性,也势必会影响到语言的发展和快速迭代,可以说这一限制是相当不利的。

所以,对于一般的 app 开发者来说,ABI 稳定其实就是一场博弈:你现在有更小的 app 尺寸,但是却被限制了无法使用最新的语言特性,除非你提升 app 的 depolyment target。

我是框架开发者,ABI 稳定后我可以用 binary 形式来发布了么?

还不能。ABI 稳定是使用 binary 发布框架的必要非充分条件。框架的 binary 在不同的 runtime 是兼容了,但是作为框架,现在是依靠一个 .swiftmodule 的二进制文件来描述 API Interface 的,这个二进制文件中包含了序列化后的 AST (更准确说,是 interface 的 SIL),以及编译这个 module 时的平台环境 (Swift 编译器版本等)。

ABI 稳定并不意味着编译工具链的稳定,对于框架来说,想要用 binary 的方式提供框架,除了 binary 本身稳定以外,还需要描述 binary 的方式 (也就是现在的 swiftmodule) 也稳定,而这正在开发中。将来,Swift 将为 module 提供文本形式的 .swiftinterface 作为框架 API 描述,然后让未来的编译器根据这个描述去“编译”出对应的 .swiftmodule 作为缓存并使用。

这一目标被称为 module stability,当达到 module stability 后,你就可以使用 binary 来发布框架了 (当然,这种 binary 框架只支持带有 ABI 稳定的 Swift runtime 的平台,也就是 iOS 12.2 及以上)。

能总结一下 ABI 稳定,或者展望一下未来么?

ABI 稳定最大的受益者应该是 Apple,这让 Apple 在自己的生态系统中,特别是系统框架中,可以使用 Swift 来进行实现。在我看来,Swift ABI 稳定为 Apple 开发平台的一场革命奠定了基础。在接下来的几年里,如果你还想要关注 Apple 平台,可能下面几件事情会特别重要:

  1. Apple 什么时候发布第一个 Swift 写的系统框架
  2. Apple 什么时候开始提供第一个 Swift only 的 API
  3. Apple 什么时候开始“锁定” Objective-C 的 SDK,不再为它增加新的 API
  4. Apple 什么时候开始用 Swift 特性更新现有的 Objective-C SDK

这些事情也许会在未来几年陆续发生。面对微软从 Win32 API 向 .Net 一路迁移,到今天的 UWP (Universal Windows Platform),Google 来势汹汹的 Fuchsia 和 Dart,Swift 是 Apple 唯一能与它们抗衡的答案。相比于微软提供的泛型和并行编程模型,Google 的 Flutter 的跨平台的先天优势,Apple 平台基于 Objective-C 的 API 的易用性已然被抛开很远。虽然 Apple 在 2014 年承诺过依然维护 Objective-C,但是经过 Swift 这五年的发展,随着 Swift ABI 的稳定,什么时候如果 Objective-C 成为了继续发展的阻碍,相信 Apple 已经有足够的理由将它抛弃。

作为 Apple 平台的从业者,我们也许正处在另一个时代变革的开端。

Buy me a coffeeBuy me a coffee
最近的文章

SwiftUI 的一些初步探索 (一)

我已经计划写一本关于 SwiftUI 和 Combine 编程的书籍,希望能通过一些实践案例帮助您快速上手 SwiftUI 及 Combine 响应式编程框架,掌握下一代客户端 UI 开发技术。现在这本书已经开始预售,预计能在 10 月左右完成。如果您对此有兴趣,可以查看 ObjC 中国的产品页面了解详情及购买。十分感谢!总览如果你想要入门 SwiftUI 的使用,那 Apple 这次给出的官方教程绝对给力。这个教程提供了非常详尽的步骤和说明,网页的交互也是一流,是觉得值得看和动手学习...…

能工巧匠集继续阅读
更早的文章

与 JOSE 战斗的日子 - 写给 iOS 开发者的密码学入门手册 (实践)

概述这是关于 JOSE 和密码学的三篇系列文章中的最后一篇,你可以在下面的链接中找到其他部分: 基础 - 什么是 JWT 以及 JOSE 理论 - JOSE 中的签名和验证流程 实践 - 如何使用 Security.framework 处理 JOSE 中的验证 (本文)这一篇中,我们会在 JOSE 基础篇和理论篇的知识架构上,使用 iOS (或者说 Cocoa) 的相关框架来完成对 JWT 的解析,并利用 JWK 对它的签名进行验证。在最后,我会给出一些我自己在实现和学习这些内容时的...…

能工巧匠集继续阅读