ENJOY 工程 Swift 3 適配
雖然 Swift 3 正式版出來很久了,但我們 ENJOY 工程遲遲未升級到 Swift 3。期間一直在用 Swift 2.3 版本進行過渡。但 Swift 3 是大勢所趨,而且 Apple 也已經表示在 Xcode 8.2 將會是最后一個支持 Swift 2.3 的版本。雖然留給開發者升級的時間并不算短,但適配這個事情還是越早越好。我們項目組經過最近一段時間的奮戰,終于在今天基本完成了 Swift 3 版本的適配工作。
Swift 3 做為一個 Grand API Change 的語言版本,對我們來說,適配并不是一個輕松的工作,畢竟我們工程是從 Swift 1.1 版本開始一直開發到現在的一個純 Swift 工程,代碼量還是相當大的。下面是我們工程在適配之前的代碼統計,其中還不包括我們出自己的 framework 代碼。
適配步驟
在 Swift 3 還是 beta 1 版本時,我們就有過一次適配的經歷,那次在將所有代碼的編譯器錯誤改完之后,工程并不能運行起來,隨之作罷。后續的 beta 陸續還有語法更新,也就沒有繼續更新了。在 Swift 3 正式版出來之后,由于這段時間內業務相關代碼變動蠻大,基礎代碼也做了很多重構工作,之前的適配代碼已經基本不可用。我將適配工作拆分成下面的步驟:
-
第三方庫及自己 Swift framework 庫的適配
-
解決 ENJOY 工程編譯錯誤
-
解決 ENJOY 工程的編譯警告
-
Swift 3 風格代碼適配
適配過程
基于之前的適配經驗,我并不相信 Xcode 自帶的升級代碼轉換工具,只是使用這個工具將工程的配置文件更新到 Swift 3,其余的源代碼均是手動更改。
首先,將 ENJOY 工程依賴的幾個自己的 framework 工程切出 swift3 分支適配 Swift 3。這個過程很順利。然后,對 Swift 3 依賴的第三方 Swift 庫進行升級,這時就發現了一個比較坑的事情,也是導致了我們 Swift 3 完全適配拖延的問題,就是 Alamofire 這個庫的 Swift 3 版本的分支最低支持的版本變成了 iOS 9!!!!這是 Alamofire 官方組織刻意為之,然后他們有一個完整解釋這么做的原因。我看了之后表示,嗯,他們說的好有道理。但,我們還是很可能要支持 iOS 8 的啊(事實是最后確實還是要支持 iOS 8)。那好,暫且決定不管它,先去適配別的。
三方庫的工作搞定后,下面要解決 Swift 3 下的編譯錯誤。從 ENJOY 工程切出了一個 swift3 分支。將工程編譯環境改為 Swift 3,并將所有第三方庫依賴切換到 Swift 3 去,然后就開始了根據代碼提示來修改語法錯誤的漫漫長路。由于我們的工程組織結構還是很清晰的。于是制定了計劃,先修改工程的基礎部分代碼,而不適配與業務緊密相關的 UI 和 model 代碼。由于要保證 swift3 分支每天與 develop 分支的同步,為了避免代碼沖突的情況,在適配期間內基本不做基礎代碼的重構工作。而且這個階段的目標很明確,就是優先解決語法錯誤,將工程先跑起來。
那次 beta 版本適配工作雖然最終都被丟棄,但我們也收獲了一些經驗。比如在那之后,我們對工程做了一些針對 Swift 3 適配的重構,最主要的就是重構掉 CGRectMake 這類的 Objective-C 風格的代碼。這樣的函數雖然在 Swift 2.3 中可以編譯,但在 Swift 3 中已經被刪除了,而對應的 Swift 版本代碼在兩個版本都是可以使用的。并且新寫的代碼不使用這種 API,這些都是我們在 code review 時重點關注的地方。
在經過漫長的時間后(其實是在評估要不要舍棄 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 來解決作用域改變的問題。
好了,解決完工程中所有的 Error 之后,由于已經有了一次適配經驗,所以對工程能夠一下運行起來并沒有報太大期望。果然, cmd + r 之后,不負眾望的出現了一個下面的編譯器錯誤:
segment fault, exit code xxxx.
查了資料之后,總結一句就是,出現這種情況,大部分都是因為 Swift 類型推斷出了問題。最后定位到問題,是由于工程中使用了 RxCocoa 的一個 bindTo 函數的 closure 中,使用了 $0 這種縮寫方式。老老實實去掉這種寫法,然后明確寫上 closure 的參數類型后,工程終于跑起來了!!!!!
嗯,雖然跑起來了,但是 Xcode 顯示的 warning 有 999+。總結了一下,warning 主要集中以下幾個方面:
-
函數返回值未使用
-
RxSwift 的 API 變化
-
SnapKit 的 API 變化
集中解決一下。然后就是對將已經運行起來的工程做回歸性功能測試了。
適配中的坑:
- nib 的事件對應的是有 @IBAction 方法簽名第一個參數記得加上 _ , 不然會 crash。
- 自定義 present 動畫的地方,由于方法簽名變化導致自定義動畫失效。(delegate 方法都是 optional 的,所有編譯器不會提示)
- 工程調用加密方法的地方,由于操作二進制數據的方式改變,適配起來還是蠻坑的
- SnapKit 庫 update 約束函數實現機制的更改,如果 update 一個不存在的約束,不會跟之前版本一樣自動添加這個約束,而是會 crash
到這里,也只是完成到了適配步驟的第三步。接下來,根據 Swift API Design Guideline 的思想進行重構才是一個持久的工作。
總結
適配總的來說技術難度不大,但工作量不小,而且由于改動基本涉及所有 Swift 文件,回歸測試是避免不了的了。而且適配工作還要找業務開發的間隙來做,這里如何把控是個關鍵。
對還未做適配的工程,如果在寫代碼時多考慮一下適配情況,對寫出的代碼進行調整,可以減少之后適配時的工作量。嗯,我還沒去看 Swift 4 的相關變化。
最后,祝自己在 Swift 適配工程師這條道路上越走越遠。不說了,繼續去做適配去了:joy:
來自:http://blog.nswebfrog.com/2016/11/03/swift3-adaption/