理論上來講,升級到一個小版本的語言和SDK的更新,應該是個比較順滑的過程。然而這次Swift 4.1 / Xcode 9.3 的升級所帶來的工作超出了預期。下面分『通過編譯』、『通過測試』以及『去除警告』三個步驟來說。
1. 通過編譯
相信這次 Swift 4.1 的升級對於有一定代碼量並且引用第三方庫源碼編譯的項目來說,需要處理的事情還不少,通過編譯就沒那麼容易,
1) 表達式太長
aView.y = bounds.height + margin - (102+20+20+36)/2.0
編譯居然出錯了:
Expression was too complex to be solved in reasonable time; consider breaking up the expression
看到這個錯誤時,筆者真的笑了又笑、笑出了聲。首先這個表達式沒那麼複雜,居然編譯器認爲在合理時間中編不過了。但同時,我充分理解其中的苦衷,因爲Swift 4.1之前版本中,對於這種『複雜』的表達式編譯的速度是非常慢的。修正方式:拆一個局部變量出來,寫成兩段。
這個編譯錯誤的出現,意味着 Swift 4.1 中,編譯速度得到了監控。我親測了一下,我們整個項目編譯時間縮短了30%以上,之前複雜類型推斷的方法需要編譯幾分鐘,這個問題在 Swift 4.1 中得到了修復。
2) Void 作爲參數
func asyncDoSth(_ completion: ((Void) -> Void)?) {
// compile error
completion?()
}
func asyncDoSth(_ completion: (() -> Void)?) {
completion?()
}
我們首先來複習一下,Void
實質上是一個空的tuple,即()
,所以第一個方法如果需要編譯過的話,completion?( () )
,可以傳入一個空 tuple。對於一般的應用場景來說,其實不太會碰到這個編譯不過的問題,因爲第二種寫法是我們的通常寫法。典型的問題場景一般與泛型框架有關。
3) ObjC 格式化類型大小
編譯不過不是 Swift 相關了,而是 Objective-C 的 格式化相關的函數報編譯錯誤了,我們使用了 IGListKit 框架,這個框架正好踩中了這個坑,官方修復速度不足夠快,那自己上了。問題的本質是 NSInteger 這樣的類型在32位和64位機器上的 size 是不一樣的,因此一個安全的格式化的方式是統一向上轉型,然後用%ld輸出。
2. 通過測試
編譯過只是第一步,新的 SDK 和 語言的升級也會帶來新的問題。這時候經過簡單自測後,需要請測試同學幫忙全量回歸測試了。不測不知道,一測嚇一跳。
1) 分享跳轉回來都掛了
func application(_ application: UIApplication, open url: URL,
sourceApplication: String?, annotation: Any) -> Bool {
由於一些衆所周知的原因,我們還需要 hook 這個已經廢棄的方法來實現應用間跳轉的流程,可是,分享回來之後,在任何函數被調用之前,居然掛了。
經過一番折騰發現,似乎是 nil 不能被轉換成 Any,WTF? 權宜之計,在 annotation 的 Any 之後加一個問號吧。筆者測試了 Xcode 9.4 Beta,發現在 Swift 4.1.1 中這個 Bug 被修復,屬於 Swift 標準庫的問題。
2) 內存同時訪問問題
第二個Bug,隱藏得就更深了,與Swift 內部實現機制有關(同時訪問)
protocol Z {
var x: Int {get set}
var y: Int {get set}
}
struct ZZZ : Z {
var x : Int
var y : Int
}
class A {
var p : Z = ZZZ(x: 2, y: 3)
func f() {
p.y = max(p.x, p.y)
}
}
A().f()
Swift 4.1 中會出現以下運行時錯誤,同時訪問了p.y
,
Simultaneous access to 0x10217f930, but modification requires exclusive access
這個問題 tricky 的地方在於,如果你直接操作類型 ZZZ,得到的是編譯時錯誤;但是如果你通過了protocol一層間接訪問,它隱藏成了運行時錯誤。
3. 去除警告
完成以上兩步,我們遷移的基本工作已經完成了,有時間的話,應當開展或者提前開始第三步,去除警告。警告不應該累積,以免不得不改的時候改動量太大。我們來看一些典型的:
- Sequence.flatMap 返回 Optional 的情況下,應該使用 compactMap
- deallocate(capacity:) 參數沒用,直接去掉
- protocol 中的 weak property 聲明中,weak應該去掉。這裏值得注意的是,原先聲明在 protocol 上的 weak property,假如在實現者上不聲明,也是沒有weak作用的。
小結
在本文中,我們討論了Swift 4.1 遷移中會碰到的問題:
- 編譯的問題和解決方案
- Swift 4.1 的 Bug 和『特性』,及其它的解決方案
- 需要注意去除的警告
- Swift 4.1 編譯速度提升了
Swift 4.1 新特性系列文章
Swift 4.1 新特性 (1) Conditional Conformance
Swift 4.1 新特性 (2) Sequence.compactMap
Swift 4.1 新特性 (3) 合成 Equatable 和 Hashable
Swift 4.1 新特性 (4) Codable的改進
Swift 4.1 新特性 (5) 關聯類型的遞歸約束