Airbnb迁移到Swift 3的实践(2)

经过第一轮QA测试,新近迁移完成的应用暴露出数十项显著问题。其中大部分问题在三人迁移团队的处理下很快得到了解决(数小时之内),具体涉及的质量应用将在后文中进行具体讨论。在最初的调整之后,剩下的是一些重要的回归测试工作,iOS团队最多留下15项潜在的问题——其中3项会引发崩溃,意味着我们需要在应用下个版本发布前进行调查。

代码转换流程

我们首先在master创建了一个新的swift-3分支。如前文所述,我们对各个模块中的代码进行逐一转换,首先是主干模块、而后逐步推进至依赖性树结构。只要有可能,我们就会尝试以并行方式进行不同模块的转换。如果不行,我们会一同讨论该如何处理以尽可能避免冲突状况。

对于各个模块,其转换流程基本如下:

在swift-3分支下创建一个新的分支。

在该模块上运行Xcode代码转换工具。

提交并推送变更。

Build。

手动修复一部分Build错误。

提交并推送变更。

Rebuild。

重复前三个步骤直到完成。

在手动进行代码更新时,我们一直秉持着“进行最直观的代码转换”这一理念。这意味着我们并不需要在转换过程中改进代码安全性。之所以选择这种思路,主要出于两个理由。其一,由于该团队以往一直利用Swift 2进行开发,因此这个过程实际上是在与时间赛跑,意味着并没有多余的精力进行质量调整。其二,我们希望尽可能减少新增的回归测试。

幸运的是,我们的项目在推进一段时间后即遇到了法定假期,这意味着我们能够腾出几天时间在master上对swift-3进行基础重建而不会导致进度延后。在进行基础重建时,我们利用git rebase -Xours master以保证尽可能不影响swift-3,同时解决master中的各类冲突。

当swift-3与master进度对接后,我们意识到需要大约一天时间整理现有问题,而后才能放心地对二者加以合并。不过考虑到iOS团队的庞大规模,master实际上一直在不断变化。因此为了完整Swift 3迁移,我们强烈建议整个iOS团队(除去参与迁移工作的成员)安心享受周末,而不要再对代码进行任何改动。

需要注意的问题

Objective-C中的Block参数

作为一大常见且无法在Xcode内得到自动修复的问题,我们发现Objective-C与Swift无法实现对block参数的顺利桥接。我们首先来看以下Objective-C标题头内的这条方法声明:

+ (void)fetchReviewWithID:(NSString *)reviewId completion:(void (^)(AIRReview *review))completionBlock

而在Swift 2.3中,生成的接口如下所示:

public class func fetchReviewWithID( reviewId: String!, completion completionBlock: ((AIRReview!) -> Void)!)

在Swift 3中,生成的接口则为:

open class func fetch( withID reviewId: String!, completion completionBlock: ((AIRReview?) -> Swift.Void)!)

很多内容都出现了变化,不过其中最重要的是completionBlock中的参数由隐式解析可选项变成了可选项。这可能破坏其在各blocks中的使用方式。

我们决定以最为直观的方式将其翻译为Swift 3(而不触及Objective-C代码),即在该block的开头声明一条变量,其拥有与该参数相同的名称但为隐式解析状态:

fetch( withID: reviewId, completion: { (review) in let review: AIRReview! = review // ... } )

如此一来,相较于在使用时对该参数进行实际解析,我们现在至少能够确保其不会破坏block内其它位置的语义。在以上示例中,if let someReview = review { /* … */ }与review ?? anotherReview等后续声明将继续按预期方式工作。

隐式解析Optional分配中的类型推断

另一大常见问题在于,我们需要设定Swift 3将变量类型推断为已分配的隐式解析Optional:

func doSomething() -> Int! { return 5 } var result = doSomething()

在Swift 2.3中,result会被推断为类型Int!。在Swift 3中,其则会被推断为类型Int?。

出于block参数概述的原因,最简单的解决办法就是将变量声明为隐式解析Optional类型:

var result: Int! = doSomething()

这一特定问题的出现频率要比预期更高,因为桥接后的Objective-C初始化工具会返回隐式解析Optional类型。

个别函数的编译时间激增

在我们的代码转换工作当中,编译器有时候会卡住几分钟。

我们的项目中存在一些 需要复杂类型推测的函数。在正常情况下,其编译时耗不会太长。但一旦其中存在编译错误,则可能令编译过程陷入混乱。

当我们的进度因为这类问题而受阻时,我们采用Build Time Analyzer for Xcode协助发现瓶颈所在。在此之后,我们开始专注于那些会给代码转换周期造成阻塞的函数,加以调整、进行rebuild再转换更多代码。

可选协议方法实现险些出现问题

在Swift 3转换过程中,可选协议方法往往很容易被大家所忽略。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/14242.html