经过第一轮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转换过程中,可选协议方法往往很容易被大家所忽略。