ENJOY 工程 Swift 3 适配

| categories: iOS | tags: iOS

虽然 Swift 3 正式版出来很久了,但我们 ENJOY 工程迟迟未升级到 Swift 3。期间一直在用 Swift 2.3 版本进行过渡。但 Swift 3 是大势所趋,而且 Apple 也已经表示在 Xcode 8.2 将会是最后一个支持 Swift 2.3 的版本。虽然留给开发者升级的时间并不算短,但适配这个事情还是越早越好。我们项目组经过最近一段时间的奋战,终于在今天基本完成了 Swift 3 版本的适配工作。

Swift 3 做为一个 Grand API Change 的语言版本,对我们来说,适配并不是一个轻松的工作,毕竟我们工程是从 Swift 1.1 版本开始一直开发到现在的一个纯 Swift 工程,代码量还是相当大的。下面是我们工程在适配之前的代码统计,其中还不包括我们自己的 framework 代码。

适配步骤


在 Swift 3 还是 beta 1 版本时,我们就有过一次适配的经历,那次在将所有代码的编译器错误改完之后,工程并不能运行起来,随之作罢。后续的 beta 版本陆续还有语法更新,也就没有继续随之更新了。在 Swift 3 正式版出来之后,由于这段时间内业务相关代码变动蛮大,代码也做了很多重构工作,之前的适配代码已经基本不可用。为了完成整个工程的适配,我将适配工作拆分成下面的步骤:

  1. 第三方库及自己 Swift framework 库的适配
  2. 解决 ENJOY 工程编译错误
  3. 解决 ENJOY 工程的编译警告
  4. Swift 3 风格代码适配

适配过程


基于之前的适配经验,我并不相信 Xcode 自带的代码转换工具,因为这个工具会在某些地方将代码修改为不想要的逻辑,而这种修改又很难被第一时间发现。于是只使用这个工具将工程的配置文件更新到 Swift 3,其余的源代码均是手动更改。

首先,将 ENJOY 工程依赖的几个自己的 framework 工程切出 swift3 分支适配 Swift 3。这个过程很顺利。然后,对 Swift 3 依赖的第三方 Swift 库进行升级,这时就发现了一个比较坑的事情,也是导致了我们 Swift 3 完全适配拖延的问题,就是 Alamofire 这个库的 Swift 3 版本的分支最低支持的版本变成了 iOS 9!!!!这个结果是 Alamofire 官方组织刻意为之,然后他们也有一个完整的说明来解释这么做的原因。我看了之后表示,嗯,他们说的好有道理。但,我们还是很可能要支持 iOS 8 的啊(事实是最后确实还是要支持 iOS 8)。那好,暂且决定不管它,先去适配别的。

三方库的工作搞定后,下面要解决 Swift 3 下的编译错误。从 ENJOY 工程切出了一个 swift3 分支。将工程编译环境改为 Swift 3,并将所有第三方库依赖切换到 Swift 3 去,然后就开始了根据代码提示来修改语法错误的漫漫长路。由于我们的工程组织结构还是很清晰的。于是制定了以下计划,先修改工程的基础部分代码,而不适配与业务紧密相关的 UI 和 model 代码。由于要保证 swift3 分支每天与 develop 分支的同步,为了避免代码冲突的情况,在适配期间内基本不做公用部分代码的重构工作。而且这个阶段的目标很明确,就是优先解决语法错误,将工程先跑起来。

那次 beta 版本适配工作虽然最终都被丢弃,但我们也收获了一些经验。比如在那之后,我们对工程做了一些针对 Swift 3 适配的重构,最主要的就是重构掉 CGRectMake 这类的 Objective-C 风格的代码。这样的函数虽然在 Swift 2.3 中可以编译,但在 Swift 3 中已经被删除了,而对应的 Swift 版本代码在两个版本都是可以使用的。并且新写的代码不使用这种 API,这些都是我们在 code review 时重点关注的地方。

在经过漫长的时间后(其实是在评估要不要舍弃 iOS 8),除了业务代码之外的部分已经适配完毕。到了适配业务代码的时候了,事实证明,我们还不能抛弃 iOS 8。看了以下 Alamofire 的代码,支持 iOS 9 的原因就是因为使用了 iOS 9 下的 stream 相关的网络 API, 而这部分功能我们工程并没有用到,那么就 fork 了一份 Alamofire 库,屏蔽掉这部分功能,并将最低支持版本改为 iOS 8。然后修改工程的 Alamofire 依赖地址。这个 fork 库的地址 点这里 ,可以将工程依赖指向这个库的 serious 分支即可使用 iOS 8 版本的 Alamofire。解决掉这个障碍之后,我们于上周启动了整个 Swift 3 适配工作。由于 Swift 3 语法的变化,适配中很重要的一个准则就是,如果函数带有参数,那么就在函数定义的第一个参数的 label 前加上 _ 来解决函数签名问题,目的就是优先让工程跑起来。其余的根据代码提示来修改就好(这里严重吐槽一下 Xcode 对 Swift 语言的语法高亮速度,不过也可能是机器太慢)。一个小技巧:熟练使用文本替换可以节省不少时间,比如可以将一个文件中所有的 private 替换为 fileprivate 来解决作用域改变的问题。

好了,解决完工程中所有的 Error 之后,由于已经有了一次适配经验,所以对工程能够一下运行起来并没有报太大期望。果然,cmd + r 之后,不负众望的出现了一个下面的编译器错误:

segment fault, exit code xxxx.

查阅资料之后,总结起来就是,出现这种情况,大部分都是因为 Swift 类型推断出了问题。最后定位到问题,是由于工程中使用了 RxCocoa 的一个 bindTo 函数的 closure 中,使用了 $0 这种参数缩写方式。老老实实去掉这种写法,然后明确写上 closure 的参数类型后,工程终于跑起来了!!!!!

嗯,虽然跑起来了,但是 Xcode 显示的 warning 有 999+。warning 主要集中以下几个方面:

  • 函数返回值未使用
  • RxSwift 的 API 变化
  • SnapKit 的 API 变化

集中解决一下。然后就是对将已经运行起来的工程做回归性功能测试了。

适配中的坑:

  • nib 的事件对应的是有 @IBAction 方法签名第一个参数记得加上 _, 不然会 crash。
  • 自定义 present 动画的地方,由于方法签名变化导致自定义动画失效。(delegate 方法都是 optional 的,所有编译器不会提示)
  • 工程调用加密方法的地方,由于操作二进制数据的方式改变,适配起来还是蛮坑的
  • SnapKit 库 update 约束函数实现机制的更改,如果 update 一个不存在的约束,不会跟之前版本一样自动添加这个约束,而是会 crash

到这里,也只是完成到了适配步骤的第三步。接下来,根据 Swift API Design Guideline 的思想进行重构才是一个持久的工作。

总结


适配总的来说技术难度不大,但工作量不小,而且由于改动基本涉及所有 Swift 文件,回归测试是避免不了的。而且适配工作还要找业务开发的间隙来做,这里如何把控是个关键。

如果你的工程还未做适配,如果在写代码时多考虑一下适配情况,可以减少之后适配时的工作量。

最后,祝自己在 Swift 适配工程师这条道路上越走越远。不说了,继续去做适配去了😂。嗯,我还没去看 Swift 4 的相关变化。




Previous     Next

Published under (CC) BY-NC-SA