ENJOY 工程 Swift 3 适配

ucro1268 8年前
   <p>虽然 Swift 3 正式版出来很久了,但我们 ENJOY 工程迟迟未升级到 Swift 3。期间一直在用 Swift 2.3 版本进行过渡。但 Swift 3 是大势所趋,而且 Apple 也已经表示在 Xcode 8.2 将会是最后一个支持 Swift 2.3 的版本。虽然留给开发者升级的时间并不算短,但适配这个事情还是越早越好。我们项目组经过最近一段时间的奋战,终于在今天基本完成了 Swift 3 版本的适配工作。</p>    <p>Swift 3 做为一个 Grand API Change 的语言版本,对我们来说,适配并不是一个轻松的工作,毕竟我们工程是从 Swift 1.1 版本开始一直开发到现在的一个纯 Swift 工程,代码量还是相当大的。下面是我们工程在适配之前的代码统计,其中还不包括我们出自己的 framework 代码。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/eb08ad205070a0ddbe77f62587795ca7.jpg"></p>    <h2><strong>适配步骤</strong></h2>    <p>在 Swift 3 还是 beta 1 版本时,我们就有过一次适配的经历,那次在将所有代码的编译器错误改完之后,工程并不能运行起来,随之作罢。后续的 beta 陆续还有语法更新,也就没有继续更新了。在 Swift 3 正式版出来之后,由于这段时间内业务相关代码变动蛮大,基础代码也做了很多重构工作,之前的适配代码已经基本不可用。我将适配工作拆分成下面的步骤:</p>    <ol>     <li> <p>第三方库及自己 Swift framework 库的适配</p> </li>     <li> <p>解决 ENJOY 工程编译错误</p> </li>     <li> <p>解决 ENJOY 工程的编译警告</p> </li>     <li> <p>Swift 3 风格代码适配</p> </li>    </ol>    <h2><strong>适配过程</strong></h2>    <p>基于之前的适配经验,我并不相信 Xcode 自带的升级代码转换工具,只是使用这个工具将工程的配置文件更新到 Swift 3,其余的源代码均是手动更改。</p>    <p>首先,将 ENJOY 工程依赖的几个自己的 framework 工程切出 swift3 分支适配 Swift 3。这个过程很顺利。然后,对 Swift 3 依赖的第三方 Swift 库进行升级,这时就发现了一个比较坑的事情,也是导致了我们 Swift 3 完全适配拖延的问题,就是 Alamofire 这个库的 Swift 3 版本的分支最低支持的版本变成了 iOS 9!!!!这是 Alamofire 官方组织刻意为之,然后他们有一个完整解释这么做的原因。我看了之后表示,嗯,他们说的好有道理。但,我们还是很可能要支持 iOS 8 的啊(事实是最后确实还是要支持 iOS 8)。那好,暂且决定不管它,先去适配别的。</p>    <p>三方库的工作搞定后,下面要解决 Swift 3 下的编译错误。从 ENJOY 工程切出了一个 swift3 分支。将工程编译环境改为 Swift 3,并将所有第三方库依赖切换到 Swift 3 去,然后就开始了根据代码提示来修改语法错误的漫漫长路。由于我们的工程组织结构还是很清晰的。于是制定了计划,先修改工程的基础部分代码,而不适配与业务紧密相关的 UI 和 model 代码。由于要保证 swift3 分支每天与 develop 分支的同步,为了避免代码冲突的情况,在适配期间内基本不做基础代码的重构工作。而且这个阶段的目标很明确,就是优先解决语法错误,将工程先跑起来。</p>    <p>那次 beta 版本适配工作虽然最终都被丢弃,但我们也收获了一些经验。比如在那之后,我们对工程做了一些针对 Swift 3 适配的重构,最主要的就是重构掉 CGRectMake 这类的 Objective-C 风格的代码。这样的函数虽然在 Swift 2.3 中可以编译,但在 Swift 3 中已经被删除了,而对应的 Swift 版本代码在两个版本都是可以使用的。并且新写的代码不使用这种 API,这些都是我们在 code review 时重点关注的地方。</p>    <p>在经过漫长的时间后(其实是在评估要不要舍弃 iOS 8),除了业务代码之外的部分已经适配完毕。到了适配业务代码的时候了,事实证明,我们还不能抛弃 iOS 8。看了以下 Alamofire 的代码,支持 iOS 9 的原因就是因为使用了 iOS 9 下的 stream 网络 API, 而这部分功能我们工程并没有用到,那么就 fork 了一份 Alamofire 库,屏蔽掉了这部分功能,并将最低支持版本改为 iOS 8。然后修改工程的 Alamofire 依赖地址。将工程依赖指向这个库的 serious 分支即可使用 iOS 8 版本的 Alamofire。解决掉这个障碍之后,我们于上周启动了整个 Swift 3 适配工作。由于 Swift 3 语法的变化,适配中很重要的一个准则就是,如果函数带有参数,那么就在函数定义的第一个参数的 label 前加上 _ 来解决函数签名问题,目的就是优先让工程跑起来。其余的根据代码提示来修改就好(这里严重吐槽一下 Xcode 对 Swift 语言的语法高亮速度,不过也可能是机器太慢)。一个小技巧:熟练使用文本替换可以节省不少时间,比如可以将一个文件中所有的 private 替换为 fileprivate 来解决作用域改变的问题。</p>    <p>好了,解决完工程中所有的 Error 之后,由于已经有了一次适配经验,所以对工程能够一下运行起来并没有报太大期望。果然, cmd + r 之后,不负众望的出现了一个下面的编译器错误:</p>    <pre>  <code class="language-swift">segment fault, exit code xxxx.</code></pre>    <p>查了资料之后,总结一句就是,出现这种情况,大部分都是因为 Swift 类型推断出了问题。最后定位到问题,是由于工程中使用了 RxCocoa 的一个 bindTo 函数的 closure 中,使用了 $0 这种缩写方式。老老实实去掉这种写法,然后明确写上 closure 的参数类型后,工程终于跑起来了!!!!!</p>    <p>嗯,虽然跑起来了,但是 Xcode 显示的 warning 有 999+。总结了一下,warning 主要集中以下几个方面:</p>    <ul>     <li> <p>函数返回值未使用</p> </li>     <li> <p>RxSwift 的 API 变化</p> </li>     <li> <p>SnapKit 的 API 变化</p> </li>    </ul>    <p>集中解决一下。然后就是对将已经运行起来的工程做回归性功能测试了。</p>    <p>适配中的坑:</p>    <ul>     <li>nib 的事件对应的是有 @IBAction 方法签名第一个参数记得加上 _ , 不然会 crash。</li>     <li>自定义 present 动画的地方,由于方法签名变化导致自定义动画失效。(delegate 方法都是 optional 的,所有编译器不会提示)</li>     <li>工程调用加密方法的地方,由于操作二进制数据的方式改变,适配起来还是蛮坑的</li>     <li>SnapKit 库 update 约束函数实现机制的更改,如果 update 一个不存在的约束,不会跟之前版本一样自动添加这个约束,而是会 crash</li>    </ul>    <p>到这里,也只是完成到了适配步骤的第三步。接下来,根据 Swift API Design Guideline 的思想进行重构才是一个持久的工作。</p>    <h2><strong>总结</strong></h2>    <p>适配总的来说技术难度不大,但工作量不小,而且由于改动基本涉及所有 Swift 文件,回归测试是避免不了的了。而且适配工作还要找业务开发的间隙来做,这里如何把控是个关键。</p>    <p>对还未做适配的工程,如果在写代码时多考虑一下适配情况,对写出的代码进行调整,可以减少之后适配时的工作量。嗯,我还没去看 Swift 4 的相关变化。</p>    <p>最后,祝自己在 Swift 适配工程师这条道路上越走越远。不说了,继续去做适配去了:joy:</p>    <p> </p>    <p>来自:http://blog.nswebfrog.com/2016/11/03/swift3-adaption/</p>    <p> </p>