??在objc時(shí)代,如果我們想在已有的協(xié)議上增加一個(gè)方法,并為實(shí)現(xiàn)該協(xié)議的類(lèi)增加一個(gè)共有的功能,一種常見(jiàn)的做法是將代碼拷貝到每一個(gè)實(shí)現(xiàn)該協(xié)議的類(lèi)中,這是一種笨拙而不便維護(hù)的方式。
??swift2.0引入了protocol extension,可以對(duì)已有的協(xié)議添加拓展,并提供默認(rèn)實(shí)現(xiàn),在所有遵守協(xié)議的實(shí)例類(lèi)型中,即使我們什么也不做,也可以編譯通過(guò)并調(diào)用默認(rèn)實(shí)現(xiàn)。
import Foundation
protocol ExampleProtocol {
func method()
}
extension ExampleProtocol {
func method() {
print("hi")
}
}
class ExampleClass :ExampleProtocol {
}
ExampleClass().method() //print hi
??在日常開(kāi)發(fā)中,還有一處可以用到extension協(xié)議拓展。objc中我們可以通過(guò)@optional關(guān)鍵字,聲明可選接口類(lèi)型,表示實(shí)例不一定要提供實(shí)現(xiàn),而swift的protocol所有的方法都必須提供實(shí)現(xiàn)。
??那么如果我們想在swift中,實(shí)現(xiàn)可選協(xié)議該怎么辦呢?一種可行的方案是在聲明protocol的時(shí)候加上關(guān)鍵字@objc,其本質(zhì)上聲明一個(gè)objc的協(xié)議,并且配合@objc optional關(guān)鍵字實(shí)現(xiàn)。
import Foundation
@objc protocol ExampleProtocol {
@objc optional func method()
}
class ExampleClass :ExampleProtocol {
} //build succeeded
??這樣聲明的協(xié)議,本質(zhì)是一個(gè)objc的協(xié)議,并且只用由class類(lèi)型實(shí)例可以遵守,對(duì)swift中struct的enum不可用。
??在swift2.0中可依靠extension提供一種更優(yōu)雅的方式。extension可以為方法編寫(xiě)默認(rèn)實(shí)現(xiàn),這樣即使我們的實(shí)力類(lèi)型不寫(xiě)任何實(shí)現(xiàn),也可用通過(guò)編譯。
protocol ExampleProtocol {
func optionalMethod() //可選
func necessaryMethod() //必須實(shí)現(xiàn)
}
extension ExampleProtocol {
func optionalMethod() {
print("hi")
}
}
class ExampleClass :ExampleProtocol {
func necessaryMethod() {
print("necessaryMethod")
}
//由于extension為optionalMethod提供了默認(rèn)實(shí)現(xiàn)
//所以我們這里可以不再重寫(xiě)對(duì)應(yīng)的實(shí)現(xiàn)
}
??同樣,如果在protocol中沒(méi)有顯示聲明的方法口,而只在extension中提供了默認(rèn)實(shí)現(xiàn),也是可以作為可選接口,并通過(guò)編譯。
protocol ExampleProtocol {
}
extension ExampleProtocol {
func optionalMethod() {
print("hi")
}
}
class ExampleClass :ExampleProtocol {
}
//build succeeded
??這種在protocol中沒(méi)有顯示聲明的方法,而直接在extension中提供了默認(rèn)實(shí)現(xiàn),容易產(chǎn)生一種迷惑。比如我們定義了這樣的一個(gè)接口和它的一個(gè)擴(kuò)展:
protocol ExampleProtocol {
//協(xié)議中顯示聲明的方法
func explicitMethod()
}
extension ExampleProtocol {
//協(xié)議中顯示聲明的方法
func explicitMethod() {
print("hi")
}
//協(xié)議中未顯示聲明的方法
func extensionMethod() {
print("hi")
}
}
class ExampleClass :ExampleProtocol {
func explicitMethod() {
print("hello")
}
func extensionMethod() {
print("hello")
}
}
??在調(diào)用的時(shí)候,沒(méi)有疑問(wèn),2個(gè)方法輸出的都是hello:
let cls = ExampleClass()
cls.explicitMethod() //hello
cls.extensionMethod() //hello
??而如果我們將cls的類(lèi)型,強(qiáng)轉(zhuǎn)為協(xié)議類(lèi)型,考慮此時(shí)的輸出是什么?
let cls = ExampleClass() as ExampleProtocol
cls.explicitMethod() //hello
cls.extensionMethod() //hi
??此時(shí)的輸出似乎出乎了我們的意料,我們都知道cls的真實(shí)類(lèi)型是ExampleClass,并且我們重寫(xiě)了協(xié)議的實(shí)現(xiàn),而運(yùn)行時(shí)卻輸出了extension中的默認(rèn)實(shí)現(xiàn)。
??產(chǎn)生這種問(wèn)題的原因是,由于我們沒(méi)有顯示聲明extensionMethod,這個(gè)方法實(shí)際上變成了可選類(lèi)型,沒(méi)有任何規(guī)定遵守該協(xié)議的類(lèi)型必須實(shí)現(xiàn),而我們的編譯器認(rèn)為extensionMethod有能未被當(dāng)前類(lèi)型的實(shí)例實(shí)現(xiàn),轉(zhuǎn)而不使用動(dòng)態(tài)派發(fā)的機(jī)制,而是在編譯期就確定調(diào)用哪個(gè)具體的實(shí)現(xiàn),以保證安全。
??而對(duì)于方法explicitMethod,由于在protocol中顯示的聲明了,因此可以確定遵守該協(xié)議的實(shí)例一定是實(shí)現(xiàn)了該方法(這里不管是重寫(xiě)實(shí)現(xiàn),還是extension的默認(rèn)實(shí)現(xiàn)),可以大膽的使用動(dòng)態(tài)派發(fā)機(jī)制,尋找方法的最終實(shí)現(xiàn)。
總結(jié)一下:
- extension允許我們?yōu)橐延袇f(xié)議增加方法或者增加方法的默認(rèn)實(shí)現(xiàn)。
- swift protocol中顯示聲明的方法,必須實(shí)現(xiàn),但extension提供了默認(rèn)實(shí)現(xiàn)的方法,實(shí)例類(lèi)型可以不提供顯示的具體實(shí)現(xiàn)(隱示實(shí)現(xiàn)了extension的實(shí)現(xiàn))。
- extension的默認(rèn)實(shí)現(xiàn)可以讓方法變?yōu)榭蛇x,即實(shí)例不需要提供最終實(shí)現(xiàn)。
- 如果方法沒(méi)有在protocol中顯示的聲明,類(lèi)型推斷得到是實(shí)例類(lèi)型將會(huì)調(diào)用實(shí)例的最終實(shí)現(xiàn)(如果有,沒(méi)有則調(diào)用extension中的默認(rèn)實(shí)現(xiàn)),而如果類(lèi)型推斷的是協(xié)議類(lèi)型,將會(huì)調(diào)用extension的默認(rèn)實(shí)現(xiàn)。
本文為讀書(shū)筆記,參考連接
PROTOCOL EXTENSION
可選接口和接口擴(kuò)展