Swift 4.1 遷移技巧

理論上來講,升級到一個小版本的語言和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) 關聯類型的遞歸約束

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章