进入Swift 3

刘承 Paul Kompfner Michael Bachand

自从该语言诞生以来,Airbnb就一直在使用Swift。 使用这种现代的,安全的,社区驱动的语言,我们已经看到了许多好处。

直到最近,我们的大部分代码库都在Swift 2中。我们刚刚完成向Swift 3的迁移,正好赶上了放弃Swift 2支持的Xcode版本。

我们希望与社区分享我们的迁移方法,Swift 3对我们的应用程序的影响以及我们在此过程中获得的一些技术见解。

“不破坏发展”的方法

我们有许多用Swift编写的模块和几个第三方库,其中包括成千上万的文件和成千上万的代码行。 似乎这个Swift代码库的大小不足以挑战,Swift 2和Swift 3模块无法相互导入这一事实使迁移过程变得更加复杂。 即使导入Swift 2库的正确Swift 3代码也不会编译。 这种不兼容使并行化代码转换变得困难。

为了确保我们可以逐步转换和验证代码,我们首先创建了一个依赖关系图,该关系图对36个Swift模块进行了拓扑排序。 我们的升级计划如下:

  1. 将CocoaPods升级到1.1.0(以支持必要的Pod升级)
  2. 将第三方Pod升级到Swift 3版本
  3. 按拓扑顺序转换我们自己的模块

通过与已经完成迁移的其他公司交谈,我们了解到冻结开发是一种常见策略。 我们希望尽可能避免代码冻结,即使这对于进行迁移的人员来说也增加了一些难度。 由于转换工作将不容易并行化,因此“多手协作”方法效率低下。 另外,由于很难估算转换所需的时间,因此我们希望确保在迁移期间可以继续发布新版本。

我们有3个人从事迁移工作。 有两个人专注于代码转换,而第三个人则专注于协调,与团队沟通和基准测试。

最后,包括准备工作,我们的实际项目时间表如下所示:

  • 1周:调查和准备(一个人)
  • 2.5周:转换(两人),分析转换的影响以及与较大团队的沟通(一个人)
  • 2周:质量检查和错误修复(质量检查小组+各种iOS功能所有者)

Swift 3的影响

尽管我们对Swift 3的新语言功能感到兴奋,但我们也想了解该更新将如何影响我们的最终用户和整体开发人员体验。 我们密切监视了Swift 3对发行IPA大小和调试构建时间的影响,因为到目前为止这是我们最大的两个Swift痛点。 不幸的是,在尝试了不同的优化设置之后,Swift 3在两个指标上的得分仍然略差。

发行IPA大小

迁移到Swift 3之后,我们看到发行版IPA增加了2.2MB。 一点挖掘表明,这几乎完全是由于Swift的库的大小增加了(我们自己的二进制文件的大小几乎没有变化)。 以下是一些我们发现未压缩二进制大小增加的示例:

  • libswiftFoundation.dylib:增长233.40%(3.8 MB)
  • libswiftCore.dylib:增长11.76%(1.5 MB)
  • libswiftDispatch.dylib:增长344.61%(0.8 MB)

鉴于Swift 3库(如Foundation)的增强,这一变化是可以理解的。 虽然,当备受期待的稳定Swift ABI着陆时,应用程序不必再受大小增加的影响而受益于这些增强功能。

调试构建时间

迁移后,我们的调试构建时间减少了4.6%,比之前的6分钟增加了16秒。

我们试图比较Swift 2和Swift 3的每函数编译时间,但是由于配置文件如此不同,因此无法得出具体结论。 但是,我们确实找到了一个函数,由于迁移,该函数的编译时间激增至12秒。 幸运的是,我们能够对其进行调整,但它向我们说明了检查转换后的代码是否存在异常值的重要性。 诸如Xcode的构建时间分析器之类的工具可以提供帮助,或者您可以仅设置适当的Swift编译器标志并解析生成的构建日志。

运行时问题

不幸的是,即使在Swift 3中编译代码后,迁移工作仍未完成。Xcode代码转换工具不能保证相同的运行时行为。 而且,正如我们稍后将讨论的那样,代码转换仍然涉及手动工作,并且存在一些陷阱。 不幸的是,这可能意味着回归。 由于我们的单元测试覆盖范围不足以使我们充满信心,因此我们不得不在新迁移的应用程序上花费额外的质量检查周期。

通过新迁移的应用程序进行的首次质量检查产生了许多相当明显的问题。 由负责迁移的3人团队迅速(在几个小时内)解决了绝大多数问题,主要是通过应用本文稍后讨论的几种技术。 在初步消除了低挂的,高度可见的回归之后,整个iOS团队剩下了15个潜在的回归-其中3个是崩溃-需要在下一个应用版本发布之前进行调查。

代码转换过程

我们首先从master创建一个新的swift-3分支。 如前所述,我们逐个模块地处理了代码转换模块,从叶子模块开始,一直到依赖树。 我们尽可能地并行转换不同的模块。 当我们做不到时,我们坐在一起,喊出我们正在做的事情,以避免发生冲突。

对于每个模块,该过程大致如下:

  1. swift-3分支创建一个新分支
  2. 在模块上运行Xcode代码转换工具
  3. 提交并推送更改
  4. 建立
  5. 手动修复许多构建错误
  6. 提交并推送更改
  7. 重建
  8. 重复前面的3个步骤,直到完成

手动更新代码时,我们坚持“进行最直接的代码转换”的理念。这意味着我们在转换过程中的目标并不是提高代码的安全性。 我们这样做有两个原因。 首先,由于该团队正在积极开发Swift 2,因此该过程是一场争分夺秒的竞赛。 其次,我们希望最大程度地降低引入回归的风险。

幸运的是,由于假期,我们在一段时间内工作缓慢,因此我们承担了这个项目。 这意味着我们可以安全地呆几天,而无需将swift-3 master而不会落后太多。 每当我们进行git rebase -Xours master ,我们都会使用git rebase -Xours master保持尽可能多的swift-3 ,同时默认使用master编码来解决冲突。

一旦swift-3master赶上了,我们就知道我们需要一天的时间来整理一些问题,然后我们才能舒适地合并它。 但是,对于拥有我们这样规模的iOS团队, master是一个移动的目标。 因此,为了完成Swift 3迁移,我们强烈鼓励整个团队(减去进行迁移的团队)真正,真正地休假星期六。

值得一提的问题

Objective-C中的块参数

我们遇到的最常见问题之一是Xcode不会自动建议修复与在Objective-C和Swift之间桥接块参数有关。 考虑在Objective-C标头中的此方法声明:

在airbnb.io上查看我们所有的开源项目,并在Twitter上关注我们:@AirbnbEng + @AirbnbData