Swift協(xié)議初識
Protocol
Swift 標準庫中有 50 多個復雜不一的協(xié)議,幾乎所有的實際類型都是滿足若干協(xié)議的。protocol 是 Swift 語言的底座,語言的其他部分正是在這個底座上組織和建立起來的徽缚。
所謂協(xié)議排宰,就是一組屬性和/或方法的定義,而如果某個具體類型想要遵守一個協(xié)議,那它需要實現(xiàn)這個協(xié)議所定義的所有這些內(nèi)容傲醉。協(xié)議實際上做的事情不過是“關于實現(xiàn)的約定”礼仗。
面向?qū)ο?/h3>
class Animal {
var leg: Int { return 2 }
func eat() {
print("eat food.")
}
func run() {
print("run with \(leg) legs")
}
}
class Tiger: Animal {
override var leg: Int { return 4 }
override func eat() {
print("eat meat.")
}
}
let tiger = Tiger()
tiger.eat() // "eat meat"
tiger.run() // "run with 4 legs"
class Animal {
var leg: Int { return 2 }
func eat() {
print("eat food.")
}
func run() {
print("run with \(leg) legs")
}
}
class Tiger: Animal {
override var leg: Int { return 4 }
override func eat() {
print("eat meat.")
}
}
let tiger = Tiger()
tiger.eat() // "eat meat"
tiger.run() // "run with 4 legs"
我們看到 Tiger
和 Animal
共享了一部分代碼,這部分代碼被封裝到了父類中,而除了 Tiger
的其他的子類也能夠使用 Animal
的這些代碼蔫饰。這其實就是 OOP 的核心思想 - 使用封裝和繼承蚪拦,將一系列相關的內(nèi)容放到一起。
這套理念有一些缺陷孩擂。雖然我們努力用這套抽象和繼承的方法進行建模,但是實際的事物往往是一系列特質(zhì)的組合,而不單單是以一脈相承并逐漸擴展的方式構(gòu)建的。
下面有幾個解決方案:
-
Copy & Paste
這是一個比較糟糕的解決方案,但是演講現(xiàn)場還是有不少朋友選擇了這個方案,特別是在工期很緊,無暇優(yōu)化的情況下。這誠然可以理解,但是這也是壞代碼的開頭籽暇。我們應該盡量避免這種做法。
-
引入 BaseViewController
在一個繼承自
UIViewController
的BaseViewController
上添加需要共享的代碼饭庞,或者干脆在UIViewController
上添加 extension舟山。看起來這是一個稍微靠譜的做法,但是如果不斷這么做拆融,會讓所謂的Base
很快變成垃圾堆泰讽。職責不明確佛玄,任何東西都能扔進Base
,你完全不知道哪些類走了Base
累澡,而這個“超級類”對代碼的影響也會不可預估惑申。 -
依賴注入
通過外界傳入一個帶有
myMethod
的對象,用新的類型來提供這個功能翅雏。這是一個稍好的方式圈驼,但是引入額外的依賴關系,可能也是我們不太愿意看到的望几。 -
多繼承
當然绩脆,Swift 是不支持多繼承的。不過如果有多繼承的話橄抹,我們確實可以從多個父類進行繼承靴迫,并將
myMethod
添加到合適的地方。有一些語言選擇了支持多繼承 (比如 C++)楼誓,但是它會帶來 OOP 中另一個著名的問題:菱形缺陷玉锌。菱形缺陷
上面的例子中,如果我們有多繼承疟羹,那么
ViewController
和AnotherViewController
的關系可能會是這樣的:img在上面這種拓撲結(jié)構(gòu)中主守,我們只需要在
ViewController
中實現(xiàn)myMethod
,在AnotherViewController
中也就可以繼承并使用它了榄融〔我看起來很完美,我們避免了重復愧杯。但是多繼承有一個無法回避的問題涎才,就是兩個父類都實現(xiàn)了同樣的方法時,子類該怎么辦力九?我們很難確定應該繼承哪一個父類的方法耍铜。因為多繼承的拓撲結(jié)構(gòu)是一個菱形邑闺,所以這個問題又被叫做菱形缺陷 (Diamond Problem)。像是 C++ 這樣的語言選擇粗暴地將菱形缺陷的問題交給程序員處理业扒,這無疑非常復雜检吆,并且增加了人為錯誤的可能性。而絕大多數(shù)現(xiàn)代語言對多繼承這個特性選擇避而遠之程储。
動態(tài)派發(fā)安全性
ViewController *v1 = ...
[v1 myMethod];
AnotherViewController *v2 = ...
[v2 myMethod];
NSObject *v3 = [NSObject new]
// v3 沒有實現(xiàn) `myMethod`
NSArray *array = @[v1, v2, v3];
for (id obj in array) {
[obj myMethod];
}
// Runtime error:
// unrecognized selector sent to instance blabla
編譯依然可以通過蹭沛,但是顯然,程序?qū)⒃谶\行時崩潰章鲤。Objective-C 是不安全的摊灭,編譯器默認你知道某個方法確實有實現(xiàn),這是消息發(fā)送的靈活性所必須付出的代價败徊。而在 app 開發(fā)看來帚呼,用可能的崩潰來換取靈活性,顯然這個代價太大了皱蹦。雖然這不是 OOP 范式的問題煤杀,但它確實在 Objective-C 時代給我們帶來了切膚之痛。
OOP的三大困境
- 動態(tài)派發(fā)安全
- 橫切關注點
- 菱形缺陷
首先沪哺,在 OC 中動態(tài)派發(fā)讓我們承擔了在運行時才發(fā)現(xiàn)錯誤的風險沈自,這很有可能是發(fā)生在上線產(chǎn)品中的錯誤。其次辜妓,橫切關注點讓我們難以對對象進行完美的建模枯途,代碼的重用也會更加糟糕。
協(xié)議拓展和面向協(xié)議編程
-
[x] 動態(tài)派發(fā)安全
```swift protocol Greetable { var name: String { get } func greet() } struct Person: Greetable { let name: String func greet() { print("你好 \(name)") } } struct Cat: Greetable { let name: String func greet() { print("meow~ \(name)") } } //以協(xié)議作為類型 //實現(xiàn)動態(tài)派發(fā) let array: [Greetable] = [ Person(name: "Wei Wang"), Cat(name: "onevcat")] for obj in array { obj.greet() } //? struct Bug: Greetable { let name: String } // Compiler Error: // 'Bug' does not conform to protocol 'Greetable' // protocol requires function 'greet()' ```
-
[ ] 橫切關注點
配合協(xié)議拓展實現(xiàn) > 協(xié)議拓展 Swift2 實現(xiàn) WWDC2015提出 ```swift protocol P { func myMethod() } //拓展協(xié)議 P extension P //提供默認實現(xiàn) func myMethod() { doWork() } } extension ViewController: P { } extension AnotherViewController: P { } viewController.myMethod() anotherViewController.myMethod() ``` 總結(jié): - 協(xié)議定義 - 提供實現(xiàn)的入口 - 遵循協(xié)議的類型需要對其進行實現(xiàn) - 協(xié)議擴展 - 為入口提供默認實現(xiàn) - 根據(jù)入口提供額外實現(xiàn)
-
[ ] 菱形缺陷
```swift protocol Nameable { var name: String { get } } protocol Identifiable { var name: String { get } var id: Int { get } } struct Person: Nameable, Identifiable { let name: String let id: Int } // `name` 屬性同時滿足 Nameable 和 Identifiable 的 name ``` 如果對協(xié)議添加了默認實現(xiàn) ```swift extension Nameable { var name: String { return "default name" } } struct Person: Nameable, Identifiable { //name 使用了 Nameable 的 name 屬性 //let name: String let id: Int } ``` 這樣的編譯是可以通過的籍滴,雖然 `Person` 中沒有定義 `name`酪夷,但是通過 `Nameable` 的 `name` (因為它是靜態(tài)派發(fā)的),`Person` 依然可以遵守 `Identifiable`孽惰。不過晚岭,當 `Nameable` 和 `Identifiable` 都有 `name` 的協(xié)議擴展的話,就無法編譯了: ```swift extension Nameable { var name: String { return "default name" } } extension Identifiable { var name: String { return "another default name" } } struct Person: Nameable, Identifiable { // let name: String let id: Int } // 無法編譯勋功,name 屬性沖突 ``` 這種情況下腥例,`Person` 無法確定要使用哪個協(xié)議擴展中 `name` 的定義。在同時實現(xiàn)兩個含有同名元素的協(xié)議酝润,**并且**它們都提供了默認擴展時,我們需要在**具體的類型中明確地提供實現(xiàn)**璃弄。這里我們將 `Person` 中的 `name` 進行實現(xiàn)就可以了: ```swift struct Person: Nameable, Identifiable { let name: String let id: Int } Person(name: "onevcat", id: 123).name // onevcat ``` 這里的行為看起來和菱形問題很像要销,但是有一些本質(zhì)不同。首先夏块,這個問題出現(xiàn)的前提條件是同名元素**以及**同時提供了實現(xiàn)疏咐,而協(xié)議擴展對于協(xié)議本身來說并不是必須的纤掸。其次,我們在具體類型中提供的實現(xiàn)一定是安全和確定的浑塞。當然借跪,菱形缺陷沒有被完全解決,**Swift 還不能很好地處理多個協(xié)議的沖突**酌壕,這是 Swift 現(xiàn)在的不足掏愁。