一于宙、OC 協(xié)議:發(fā)消息
OC 的協(xié)議本質(zhì)是消息的集合浮驳。例如,UITableViewDataSource 協(xié)議有請(qǐng)求 sections 個(gè)數(shù)和一個(gè) sections 中的 rows 的個(gè)數(shù)捞魁。
二至会、Swift 協(xié)議:消息 + 關(guān)聯(lián)類型
Swift 協(xié)議也是消息的集合。Swift 的協(xié)議同時(shí)也包含關(guān)聯(lián)類型谱俭。協(xié)議中的關(guān)聯(lián)類型是類型占位符奉件,實(shí)現(xiàn)協(xié)議的時(shí)候來(lái)填充具體的類型。
三旺上、關(guān)聯(lián)類型:簡(jiǎn)化協(xié)議的實(shí)現(xiàn)
關(guān)聯(lián)類型是一個(gè)強(qiáng)大的工具瓶蚂,讓我們更方便的實(shí)現(xiàn)協(xié)議。
例子:Swift Equatable 協(xié)議
例如宣吱,Swift Equatable 協(xié)議有一個(gè)比較兩個(gè)值是否相等的函數(shù)窃这。
static func ==(lhs: Self, rhs: Self) -> Bool
這個(gè)函數(shù)使用了 Self 這個(gè)關(guān)聯(lián)類型。Self 的具體類型是由協(xié)議的具體實(shí)現(xiàn)來(lái)確定征候。假設(shè)有一個(gè)類型下面的結(jié)構(gòu)體杭攻,那么這個(gè)時(shí)候 Self 就是 Name。
struct Name: Equatable {
let value: String
}
這個(gè)時(shí)候疤坝,協(xié)議的具體實(shí)現(xiàn)如下:
static func ==(lhs: Name, rhs: Name) -> Bool
綜上:Equatable 使用關(guān)聯(lián)類型 Self 來(lái)約束比較相同類型的值的 == 函數(shù)兆解。
四、對(duì)比:OC NSObjectProtocol
NSObjectProtocol 也有一個(gè) isEqual(_:)方法跑揉,但是因?yàn)槭?OC 的協(xié)議锅睛,不能用 Self 類型。具體的定義如下:
func isEqual(_ object: Any?) -> Bool
OC 的協(xié)議無(wú)法約束參數(shù)類型為指定關(guān)聯(lián)類型历谍,因此所有遵守協(xié)議的類型都能用來(lái)比較现拒。通常在實(shí)現(xiàn)中會(huì)先檢測(cè)參數(shù)的類型與消息的接收者類型是否一致。
func isEqual(_ object: Any?) -> Bool {
guard let other = object as? Name else { return false }
// 開(kāi)始檢查值是否相等
}
每一個(gè) isEqual(_:) 的實(shí)現(xiàn)望侈,在每次調(diào)用的時(shí)候都要做這樣的檢查印蔬。Equatable 協(xié)議不會(huì)做這樣的檢測(cè),通過(guò) Self 關(guān)聯(lián)類型保證了對(duì)象滿足條件脱衙。
五侥猬、代價(jià)
Protocol ‘SomeProtocol’ can only be used as a generic constraint because it has Self or associated type requirements.
因?yàn)樾枰獙?shí)現(xiàn) Self 或者其他關(guān)聯(lián)類型,該協(xié)議只能用做泛型約束捐韩。
關(guān)聯(lián)類型是個(gè)強(qiáng)大的工具退唠。所付出的代價(jià)就是
error: protocol 'Equatable' can only be used as a generic constraint because it has Self or associated type requirements
協(xié)議中使用關(guān)聯(lián)類型,必須用做泛型荤胁。
泛型
也是一個(gè)占位符铜邮,調(diào)用泛型函數(shù)的時(shí)候,必須填充對(duì)應(yīng)的類型。
從泛型
和關(guān)聯(lián)類型
的調(diào)用和實(shí)現(xiàn)兩個(gè)方面對(duì)比來(lái)看:
關(guān)聯(lián)類型:實(shí)現(xiàn)方指定類型松蒜,調(diào)用方不指定。當(dāng)你實(shí)現(xiàn)一個(gè)使用關(guān)聯(lián)類型的函數(shù)的時(shí)候已旧,你需要填充對(duì)應(yīng)的類型秸苗,所以你知道實(shí)際的類型。調(diào)用方不知道你具體采用的類型运褪。
泛型:調(diào)用方指定類型惊楼,實(shí)現(xiàn)方不指定。當(dāng)你實(shí)現(xiàn)一個(gè)使用泛型的函數(shù)秸讹,不需要知道調(diào)用方具體采用的類型檀咙。你可以使用約束限制類型,但是你必須處理所有滿足約束的類型璃诀。調(diào)用方指定具體的類型弧可,但是你的代碼必須處理傳遞的任意類型。
例子: 調(diào)用 Equatable == 強(qiáng)制使用泛型
func checkEquals(left: Equatable, right: Equatable) -> Bool {
return left == right
}
Swift 編譯器調(diào)出如下錯(cuò)誤:
error: MyPlaygroundSwift.playground:27:24: error: protocol 'Equatable' can only be used as a generic constraint because it has Self or associated type requirements
func checkEquals(left: Equatable, right: Equatable) -> Bool {
^
error: MyPlaygroundSwift.playground:27:42: error: protocol 'Equatable' can only be used as a generic constraint because it has Self or associated type requirements
func checkEquals(left: Equatable, right: Equatable) -> Bool {
為什么不使用泛型劣欢,checkEquals 不起作用棕诵?
如果 swift 允許這樣做會(huì)怎么樣,我們來(lái)做個(gè)實(shí)驗(yàn)凿将。
假如有兩個(gè)遵守 Equatable 的類型校套,Name 和 Age。代碼可能會(huì)是這樣牧抵。
let name = Name(value: "")
let age = Age(value: 0)
let isEquals = checkEquals(name, age)
這樣做沒(méi)有任何意義笛匙,從以下兩個(gè)方面來(lái)看:
行為:怎樣運(yùn)行這段代碼?怎樣實(shí)現(xiàn) checkEquals 中的 == 調(diào)用犀变。只有 (Names, Names) 和 (Age, Age) 才有意義妹孙,因?yàn)?Equatable 定義為 ==(Self, Self)。只調(diào)用 Name 或者 Age 的 == 會(huì)打破類型安全弛作。
意義:有什么意義? Equatable 協(xié)議不僅僅是個(gè)類型涕蜂,它還和另一個(gè)類型 Self 有關(guān)。如果你寫
checkEquals(left: Equatable, right: Equatable)
映琳,只是在討論Equatable
机隙,它的關(guān)聯(lián)類型Self
被忽略。不能只是明確Equtable
萨西,必須明確Equtable where Self is (some type)
這非常蛋疼有鹿,但是很重要,checkEquals 看起來(lái)會(huì)工作谎脯。你想比較兩個(gè) Equtable 類型葱跋。但是Equtable
是一個(gè)不完整的類型,它其實(shí)是equtable for some type
checkEquals(left: Equatable, right: Equatable)
中左邊是一個(gè)equtable for some type
,右邊也是一個(gè)equtable for some type
娱俺。左右兩邊并不是equatable for the same type
稍味。Equatable ==
需要左右同一個(gè)類型,所以上面的情況checkEquals
不起作用荠卷。
使 checkEquals 處理所有的 Equatable + Self
checkEquals
在Equatable where Self is (some type)
不知道具體的some type
模庐。所以,必須處理所有的Equatable 和 Self 類型
油宜,checkEquals
所有的類型T
掂碱,T
是Equatable和它的關(guān)聯(lián)類型
具體的代碼如下:
func checkEquals<T: Equatable>(left: T, right: T) -> Bool {
return left == right
}
現(xiàn)在,類型 T
是一個(gè)Equatable
類型慎冤,T
的關(guān)聯(lián)類型Self
疼燥,實(shí)現(xiàn)了checkEquatable
方法。使用 Swift 的泛型為具體的類型實(shí)現(xiàn)了一個(gè)配方
蚁堤,不需要寫具體的checkEquals(left: Name, right: Name)
和checkEquals(left: Age, right: Age)
醉者。最后需要提取泛型函數(shù)來(lái)重構(gòu)代碼。
例子:調(diào)用 NSObjectProtocol 的 isEqual(_:) 不需要泛型
使用NSObjectProtocol
的checkEquals
不需要使用泛型违寿。
import Foundation
func checkEquals(
left: NSObjectProtocol,
right: NSObjectProtocol
) -> Bool {
return left.isEqual(right)
}
寫起來(lái)很簡(jiǎn)單湃交,這種情況下還是允許我們以下調(diào)用:
let isEqual = checkEquals(name, age)
Name 可以和 Age 比較??當(dāng)然不能藤巢,isEqual
結(jié)果是false
搞莺。Name.isEqual(_:) 會(huì)判斷對(duì)象是不是 Name 類型。不像Equabable
的==
掂咒,所以每個(gè)isEqual(:)
方法必須處理類型一致性才沧。
六、權(quán)衡
關(guān)聯(lián)類型使 Swift 的協(xié)議比 OC 的更加強(qiáng)大绍刮。
OC 的協(xié)議捕獲了對(duì)象和它的調(diào)用者間的關(guān)系温圆。調(diào)用者能夠使用協(xié)議發(fā)消息,消息的具體行為由遵守協(xié)議的對(duì)象來(lái)實(shí)現(xiàn)孩革。
Swift 協(xié)議也能夠捕獲一個(gè)類型和多個(gè)關(guān)聯(lián)類型之間的關(guān)系岁歉。Equatable
協(xié)議通過(guò)Self
關(guān)聯(lián)一個(gè)類型。SetAlgebra
協(xié)議將實(shí)現(xiàn)關(guān)聯(lián)到類型Element
膝蜈。
通過(guò)對(duì)比Equatable
的==
和NSObjectProtocol
的isEqual(:)
锅移,能夠發(fā)現(xiàn) Swift 實(shí)現(xiàn)協(xié)議比較簡(jiǎn)單,但是在使用協(xié)議的時(shí)候比較復(fù)雜饱搏。強(qiáng)大的實(shí)現(xiàn)也會(huì)使代碼變得很復(fù)雜非剃,所以在使用協(xié)議的時(shí)候,需要權(quán)衡使用關(guān)聯(lián)類型的價(jià)值和處理他們的復(fù)雜程度推沸。
希望通過(guò)這篇文章幫助你認(rèn)識(shí)使用的協(xié)議和 API备绽。如果你覺(jué)得有用券坞,可以關(guān)注我們的高級(jí) Swift 訓(xùn)練營(yíng)。
七肺素、刨根問(wèn)底:Self 是關(guān)聯(lián)類型么恨锚?
Self 的行為很像關(guān)聯(lián)類型,不同于其他關(guān)聯(lián)類型倍靡,不需要指定 Self 關(guān)聯(lián)的類型眠冈,Self 會(huì)自動(dòng)關(guān)聯(lián)到實(shí)現(xiàn)協(xié)議的類型。
但是協(xié)議中的錯(cuò)誤提示是“有 Self 或者關(guān)聯(lián)類型”菌瘫,聽(tīng)起來(lái)很像是不同的類型。
為了找到答案布卡,我找到了 Swift 編譯器中 AST 的源碼雨让,具體的文檔在這里
每個(gè)協(xié)議有一個(gè)隱式構(gòu)造的關(guān)聯(lián)類型 Self,描述了遵守協(xié)議的類型忿等。
所以栖忠,Self 是一個(gè)關(guān)聯(lián)類型
八、感謝原作者Jeremy Sherman
贸街,原文地址
https://www.bignerdranch.com/blog/why-associated-type-requirements-become-generic-constraints/