Swift 4迁移总结:喜忧参半,新的起点
每日一篇优秀博文
这次Swift 3 到 4 的迁移代码要改动的地方比较少,花了一个下午的时间就完成了迁移。Swift 把原来 4.0 的目标从 ABI 稳定改为了源码兼容,此次代码的兼容性做的确实很好,这个目标算是达到了。然而对于一个成熟的项目而言,单纯语法上的兼容并不是全部,这次的升级也带来了一些新的变化。
3.2 和 4.0
在 3.0 的时候 swift 也提供了 2.3 和 3.0 两个版本,这次 4.0 也是提供了 3.2 版本。从我项目里的代码来看,从 3.0 到 3.2 要做的改动几乎没有,只是需要重新编译一次。社区的反应来看兼容 3.2 也没反馈出什么大的改动。所以对于推迟跟进 4.0 的团队来言会是一个很顺滑的过度,可以安心的切换到 Xcode 9。
为了让大家在迁移过程中更加顺利,swift 的 framework 支持 3.2 和 4.0 版本混编。如果你有好几个组件,可以单独为某个组件升级到 4.0 。这样大的团队可以不用一口气所有的代码都迁移到 4.0。
然而 3.2 和 4.0 的兼容并没有看上去那么美好。
首先是cocoapods的问题,反正在目前pods的framework只能指定一个版本的swift。issue在这里:Pods automatically compiling with Swift 4.0 in Xcode 9 beta 1 。pod默认会使用一个swift版本编译全部,需要对不同的库单独指定swift版本。
遇到这个问题后,我把所有组件都迁移到了 4.0 ,app 因为有很多业务代码,希望先迁到 3.2 ,这样可以尽早支持 Xcode 9,同事可以尽早适配 iOS 11。然而。。。
Xcode 果然没让我失望啊,编译的时候没有错误提示只告诉你失败了:
compiling as Swift 3.2, with 'xxx' build as Swift 4.0(this is supported but may expose additional compiler issues)
提示也很清奇,我翻译一下:我们虽然支持混编,但是也可能混出毛病,所以你还是别混了。不过听闻丁香园的项目目前是主 app 4.0,组件 3.2 混编是成功的。所以说呢,如果要混可以碰碰运气。
顺便说下Xcode 9新的编译系统,我这里根本编译不过(可能因为我们混OC?),而且没有任何错误提示。苹果的软件质量果然是独领风骚,面向运气编程。
社区跟进及时
所以实际上,你要不然就全不动,停在 3.2。一旦要迁移就需要全部迁移到 4.0 。
好在这次因为语法改动小,我用到的大部分 Swift 库都支持了 4.0。弃更的只有 DOFavoriteButton ,一个点赞的控件。跟进比较慢的有 RxGesture、EZSwiftExtensions,不过已经也有了 4.0 的分支,也有人提了 4.0 的 PR,估计过几天应该也能合进去。只能说维护的人不够积极。
下面把我用到的 pod 贴出来:
3.2 | 4.0 | |
---|---|---|
RxSwift/RxCocoa | 无 | 4.0.0-beta.0 |
SnapKit | 无 | 4.0.0 |
CryptoSwift | 0.7.0 | 0.7.1 |
Alamofire | 4.5.1 | 4.5.1兼容(5.0还未发布) |
ObjectMapper | 2.2.9 | 3.0 |
SwiftyAttributes | 3.2.0 | 4.0.0 |
Kingfisher | 4.1.0 | 4.0 |
MonkeyKing | 无 | 1.4.0 |
通常情况下3.0是可以直接在3.2下编译的,所以“无”并不表示不能使用,指开发者没有单独声明一个版本兼容3.2。
4.0 开始与 OC 正式分道扬镳
为了照顾原有的开发者,Swift 2.0 的时候要做到的目标是与 OC 尽量兼容,除了几个基础的数据类型比如 Int、String 与OC不同外,其他的 API 都和 OC 保持一致,完全可以用 OC 的习惯写 Swift 。到 3.0 的时候Swift 体系开始独自进化,开始有自己的命名规范。
到 4.0 的时候,Xcode 用 Swift 重写了编译器,虽然 New build system 目前还在 preview,也确实有很多问题,然而针对 Swift 的编译优化又取得了不小的提升。我已经能感觉到苹果想要抛弃 OC 的意思,至少是非常明显的嫌弃的意思。
从代码层面来看,原先一个类只要是 NSObject 的子类,默认这个类的所有的属性方法都会自动添加给 OC 调用的 bridge,在 4.0 里这个功能被关闭了。这也是迁移 4.0 的一个比较大的工作量(对于和 OC 混编的项目)。什么意思呢,以前我们用 Swift 自定义了一个控件,原先在 OC 中引入 module 的头文件后,可以调用到这个控件公开的所有属性、方法。但是迁移到 4.0 后,所有属性、方法默认都是不能访问到,需要到控件里给要暴露给 OC 使用的属性方法前加上 @objc 。
这个改动影响非常深远。这等于是让开发者二选一了。如果我们用 Swift 写一个组件,需要支持 OC 加上 @objc 标志,编译时就要生成给 OC 调用的声明,这降低了一些些性能。但是不加的话 OC 又调用不到。更深的原因是,在写组件的时候我们并不确切的知道业务方是用 OC 还是 Swift 调用的。除非业务代码全是 Swift。或者只能全盘做 OC 桥接,到处都是 @objc,如果上面调用的是 Swift,这些又白加了。
这里还出现了另外一个细节。如果我们在 OC 里给 UIView 声明了一个属性 size,在 Swift 里也声明了一个属性 size。如果是在一个 framework 里,会编译失败提示冲突了。然而如果这两个写在不同的 framework 里,Xcode 不会提示。在 8 的时候,这个 size 的调用最后会走到 OC 的方法,但是在 9 的时候,在 Swift 代码里引入这个 OC framework,代码就直接崩溃了,会不知道应该调用那个库的 size。这显然是编译器的一个 bug,但这也侧面反映了,OC 和 Swift 混编带来的问题越来越多,两个体系的区别会越来越大。
总结
迁移到 4.0 的代价比之前要小的多, ABI 稳定很大可能在 5.0 到来。对于观望 Swift 是否稳定的开发者而言是个好消息,相信 Swift 的接受程度会更高。Swift 一年一个版本的升级和 OC 的分野会越来越大,给混编带来了很多的不确定性,对于混编的项目有能力的还是把一些代码迁移到 Swift。
对于 Xcode 我有一个经验再次和大家分享一下:Xcode 有两个版本,一个不稳定的版本和一个更不稳定的版本。