為什么 Swift 關(guān)聯(lián)類型的協(xié)議需要做為泛型約束使用(譯)

一于宙、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

checkEqualsEquatable where Self is (some type)不知道具體的some type模庐。所以,必須處理所有的Equatable 和 Self 類型油宜,checkEquals所有的類型T掂碱,TEquatable和它的關(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(_:) 不需要泛型

使用NSObjectProtocolcheckEquals不需要使用泛型违寿。

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==NSObjectProtocolisEqual(:)锅移,能夠發(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/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末庵寞,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子薛匪,更是在濱河造成了極大的恐慌捐川,老刑警劉巖,帶你破解...
    沈念sama閱讀 223,126評(píng)論 6 520
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件逸尖,死亡現(xiàn)場(chǎng)離奇詭異古沥,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)娇跟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,421評(píng)論 3 400
  • 文/潘曉璐 我一進(jìn)店門岩齿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人苞俘,你說(shuō)我怎么就攤上這事盹沈。” “怎么了吃谣?”我有些...
    開(kāi)封第一講書人閱讀 169,941評(píng)論 0 366
  • 文/不壞的土叔 我叫張陵乞封,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我基协,道長(zhǎng)歌亲,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 60,294評(píng)論 1 300
  • 正文 為了忘掉前任澜驮,我火速辦了婚禮陷揪,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己悍缠,他們只是感情好卦绣,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,295評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著飞蚓,像睡著了一般滤港。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上趴拧,一...
    開(kāi)封第一講書人閱讀 52,874評(píng)論 1 314
  • 那天溅漾,我揣著相機(jī)與錄音,去河邊找鬼著榴。 笑死添履,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的脑又。 我是一名探鬼主播暮胧,決...
    沈念sama閱讀 41,285評(píng)論 3 424
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼问麸!你這毒婦竟也來(lái)了往衷?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 40,249評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤严卖,失蹤者是張志新(化名)和其女友劉穎席舍,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體妄田,經(jīng)...
    沈念sama閱讀 46,760評(píng)論 1 321
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡俺亮,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,840評(píng)論 3 343
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了疟呐。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片脚曾。...
    茶點(diǎn)故事閱讀 40,973評(píng)論 1 354
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖启具,靈堂內(nèi)的尸體忽然破棺而出本讥,到底是詐尸還是另有隱情,我是刑警寧澤鲁冯,帶...
    沈念sama閱讀 36,631評(píng)論 5 351
  • 正文 年R本政府宣布拷沸,位于F島的核電站,受9級(jí)特大地震影響薯演,放射性物質(zhì)發(fā)生泄漏撞芍。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,315評(píng)論 3 336
  • 文/蒙蒙 一跨扮、第九天 我趴在偏房一處隱蔽的房頂上張望序无。 院中可真熱鬧验毡,春花似錦、人聲如沸帝嗡。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 32,797評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)哟玷。三九已至狮辽,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間巢寡,已是汗流浹背喉脖。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,926評(píng)論 1 275
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留抑月,地道東北人动看。 一個(gè)月前我還...
    沈念sama閱讀 49,431評(píng)論 3 379
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像爪幻,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子须误,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,982評(píng)論 2 361