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 有兩個版本,一個不穩定的版本和一個更不穩定的版本。