Swift 4.2 新特性詳解 Conditional Conformance 的更新
隨著 Xcode 10 的正式版釋出,Swift 4.2 也正式問世,在 Swift 4.1 中引入的 Conditional Conformance 也有了一個小的升級,使用便利性再次提升。不瞭解 Swift 4.1 的同學也沒有關係,本篇文章會針對 Conditional Conformance 進行完整的梳理。
首先我們對 Conditional Conformance 下個定義:
Conditional conformances express the notion that a generic type will conform to a particular protocol only when its type arguments meet certain requirements.
Conditional Conformance 表達的含義是:當一個泛型型別的型別引數滿足某些條件時,該泛型型別實現了某個特定協議。舉個例子,當 Array 型別的型別引數是 Equatable 的時候,我們希望 Array 自動成為 Equatable,具體以 extension 語法表達,如下:
extension Array: Equatable where Element: Equatable { static func == (lhs: Array,rhs: Array) -> Bool { guard lhs.count == rhs.count else { return false } for (i, v) in lhs.enumerated() { if rhs[i] != v { return false } } return true } }
這個擴充套件不僅可以用來直接比較一維陣列,由於可以遞迴推斷,同樣可以比較多維陣列:下面的 a1 和 a2 都是 Array<Array<Int>> 型別 ,由於 Int 是 Equatable ,所以 Array<Int> 也是 Equatable ,因此 Array<Array<Int>> 是Equatable,可以直接比較是否相等。
let a1 = [[123], [456,789]] let a2 = [[123], [456,789]] let a3: [[Int]] = [] print(a1 == a2) // true print(a1 == a3) // false
關於 Conditional Conformance 還有一個非常重要的特性,就是它對於執行時 Conditional Conformance 的支援。來看下面的程式碼:
protocol P { func doSomething() } struct S: P { func doSomething() { print("S") } } extension Array: P where Element: P { func doSomething() { for value in self { value.doSomething() } } } // compile-time func doSomethingStatically<E: P>(_ value:Array<E>) { value.doSomething() } // runtime func doSomethingIfP(_ value: Any) { if let p = value as? P { p.doSomething() } } doSomethingIfP([S(), S(), S()])
我們看到了兩個版本,前者是編譯器確定了這個擴充套件的有效性;後者是 runtime 的時候做檢查,它體現了 Conditional Conformance 對動態檢查的支援。
如何來直觀感受 Conditional Conformance 執行時特性的作用呢?我們可以想象是自己是被傳入的引數:我是個普通的 Array,只不過我的元素型別實現了 P。結果可以動態發現我作為 Array 也實現了 protocol P,並且擁有了新方法doSomething
。哪怕我是被作為 Any 型別傳入的,動態也能判斷上述事實。我只是個 Array ,元素型別實現了 P 而已。 Conditional Conformance 的擴充套件使這一切成為可能。
這裡我們回顧一個知識點,為什麼我們最後需要新寫個 protocol P 來舉例,而不直接用前面的 Equatable 呢?
用另一個問題可以回答:在Swift中,可不可以寫as? Equatable
,或者var e : Equatable = 10
呢?其實是不能的,因為Equatable這樣的protocol只能作為泛型的型別約束,而不能作為可以直接hold值的型別,原因是它有 associated type 或者Self
;也沒有Equatable<Int> 的寫法,Equatable 不是泛型型別,只是泛型約束。
事實上,從 Swift 4.1 開始標準庫就加入了一系列 Equatable、Encodable、Decodable 的 Conditional Conformance,例如:
extension Optional: Equatable where Wrapped: Equatable { /* ... */ } extension Array: Equatable where Element: Equatable { /* ... */ } extension Dictionary: Equatable where Value: Equatable { /* ... */ } extension Array: Encodable where Element: Encodable { /* ... */ } extension Array: Decodable where Element: Decodable { /* ... */ }
Swift 4.2 中對 Hashable 也內建了一系列 Conditional Conformance:也就是當其型別引數是 Hashable 的時候 Optional, Array, Dictionary 和 Range 也是 Hashable,非常便利的一個改進。
小結
- 介紹了 Conditional Conformance 的語法和語義
- 以 Array 為例介紹了 Conditional Conformance 用途
- Conditional Conformance 支援執行時判斷
- 標準庫內建 Array 和 Hashable 的 Conditional Conformance