Swift5.1—關(guān)聯(lián)類型

定義一個協(xié)議時,聲明一個或多個關(guān)聯(lián)類型作為協(xié)議定義的一部分將會非常有用。關(guān)聯(lián)類型為協(xié)議中的某個類型提供了一個占位符名稱,其代表的實際類型在協(xié)議被遵循時才會被指定。關(guān)聯(lián)類型通過 associatedtype 關(guān)鍵字來指定。

關(guān)聯(lián)類型實踐

下面例子定義了一個 Container 協(xié)議,該協(xié)議定義了一個關(guān)聯(lián)類型 Item

protocol Container {
    associatedtype Item
    mutating func append(_ item: Item)
    var count: Int { get }
    subscript(i: Int) -> Item { get }
}

Container 協(xié)議定義了三個任何遵循該協(xié)議的類型(即容器)必須提供的功能:

  • 必須可以通過 append(_:) 方法添加一個新元素到容器里。
  • 必須可以通過 count 屬性獲取容器中元素的數(shù)量,并返回一個 Int 值。
  • 必須可以通過索引值類型為 Int 的下標(biāo)檢索到容器中的每一個元素。

該協(xié)議沒有指定容器中元素該如何存儲以及元素類型。該協(xié)議只指定了任何遵從 Container 協(xié)議的類型必須提供的三個功能。遵從協(xié)議的類型在滿足這三個條件的情況下,也可以提供其他額外的功能。

任何遵從 Container 協(xié)議的類型必須能夠指定其存儲的元素的類型。具體來說,它必須確保添加到容器內(nèi)的元素以及下標(biāo)返回的元素類型是正確的。

為了定義這些條件,Container 協(xié)議需要在不知道容器中元素的具體類型的情況下引用這種類型。Container 協(xié)議需要指定任何通過 append(_:) 方法添加到容器中的元素和容器內(nèi)的元素是相同類型,并且通過容器下標(biāo)返回的元素的類型也是這種類型。

為此,Container 協(xié)議聲明了一個關(guān)聯(lián)類型 Item,寫作 associatedtype Item。協(xié)議沒有定義 Item 是什么,這個信息留給遵從協(xié)議的類型來提供。盡管如此,Item 別名提供了一種方式來引用 Container 中元素的類型,并將之用于 append(_:) 方法和下標(biāo),從而保證任何 Container 的行為都能如預(yù)期。

這是前面非泛型版本 IntStack 類型,使其遵循 Container 協(xié)議:

struct IntStack: Container {
    // IntStack 的原始實現(xiàn)部分
    var items = [Int]()
    mutating func push(_ item: Int) {
        items.append(item)
    }
    mutating func pop() -> Int {
        return items.removeLast()
    }
    // Container 協(xié)議的實現(xiàn)部分
    typealias Item = Int
    mutating func append(_ item: Int) {
        self.push(item)
    }
    var count: Int {
        return items.count
    }
    subscript(i: Int) -> Int {
        return items[i]
    }
}

IntStack 結(jié)構(gòu)體實現(xiàn)了 Container 協(xié)議的三個要求,其原有功能也不會和這些要求相沖突。

此外,IntStack 在實現(xiàn) Container 的要求時,指定 ItemInt 類型,即 typealias Item = Int,從而將 Container 協(xié)議中抽象的 Item 類型轉(zhuǎn)換為具體的 Int 類型。

由于 Swift 的類型推斷,實際上在 IntStack 的定義中不需要聲明 ItemInt。因為 IntStack 符合 Container 協(xié)議的所有要求,Swift 只需通過 append(_:) 方法的 item 參數(shù)類型和下標(biāo)返回值的類型,就可以推斷出 Item 的具體類型。事實上,如果你在上面的代碼中刪除了 typealias Item = Int 這一行,一切也可正常工作,因為 Swift 清楚地知道 Item 應(yīng)該是哪種類型。

你也可以讓泛型 Stack 結(jié)構(gòu)體遵循 Container 協(xié)議:

struct Stack<Element>: Container {
    // Stack<Element> 的原始實現(xiàn)部分
    var items = [Element]()
    mutating func push(_ item: Element) {
        items.append(item)
    }
    mutating func pop() -> Element {
        return items.removeLast()
    }
    // Container 協(xié)議的實現(xiàn)部分
    mutating func append(_ item: Element) {
        self.push(item)
    }
    var count: Int {
        return items.count
    }
    subscript(i: Int) -> Element {
        return items[i]
    }
}

這一次,占位類型參數(shù) Element 被用作 append(_:) 方法的 item 參數(shù)和下標(biāo)的返回類型。Swift 可以據(jù)此推斷出 Element 的類型即是 Item 的類型。

擴展現(xiàn)有類型來指定關(guān)聯(lián)類型

在擴展添加協(xié)議一致性 中描述了如何利用擴展讓一個已存在的類型遵循一個協(xié)議,這包括使用了關(guān)聯(lián)類型協(xié)議。

Swift 的 Array 類型已經(jīng)提供 append(_:) 方法,count 屬性,以及帶有 Int 索引的下標(biāo)來檢索其元素。這三個功能都符合 Container 協(xié)議的要求,也就意味著你只需聲明 Array 遵循Container 協(xié)議,就可以擴展 Array,使其遵從 Container 協(xié)議。你可以通過一個空擴展來實現(xiàn)這點,正如通過擴展采納協(xié)議中的描述:

extension Array: Container {}

Arrayappend(_:) 方法和下標(biāo)確保了 Swift 可以推斷出 Item 具體類型。定義了這個擴展后,你可以將任意 Array 當(dāng)作 Container 來使用。

給關(guān)聯(lián)類型添加約束

你可以在協(xié)議里給關(guān)聯(lián)類型添加約束來要求遵循的類型滿足約束。例如,下面的代碼定義了 Container 協(xié)議, 要求關(guān)聯(lián)類型 Item 必須遵循 Equatable 協(xié)議:

protocol Container {
    associatedtype Item: Equatable
    mutating func append(_ item: Item)
    var count: Int { get }
    subscript(i: Int) -> Item { get }
}

要遵守 Container 協(xié)議,Item 類型也必須遵守 Equatable 協(xié)議。

在關(guān)聯(lián)類型約束里使用協(xié)議

協(xié)議可以作為它自身的要求出現(xiàn)。例如,有一個協(xié)議細(xì)化了 Container 協(xié)議,添加了一個suffix(_:) 方法。suffix(_:) 方法返回容器中從后往前給定數(shù)量的元素,并把它們存儲在一個 Suffix 類型的實例里。

protocol SuffixableContainer: Container {
    associatedtype Suffix: SuffixableContainer where Suffix.Item == Item
    func suffix(_ size: Int) -> Suffix
}

在這個協(xié)議里,Suffix 是一個關(guān)聯(lián)類型,就像上邊例子中 ContainerItem 類型一樣。Suffix 擁有兩個約束:它必須遵循 SuffixableContainer 協(xié)議(就是當(dāng)前定義的協(xié)議),以及它的 Item 類型必須是和容器里的 Item 類型相同。Item 的約束是一個 where 分句,它在下面 具有泛型 Where 子句的擴展 中有討論。

這是上面 泛型類型Stack 類型的擴展,它遵循了 SuffixableContainer 協(xié)議:

extension Stack: SuffixableContainer {
    func suffix(_ size: Int) -> Stack {
        var result = Stack()
        for index in (count-size)..<count {
            result.append(self[index])
        }
        return result
    }
    // 推斷 suffix 結(jié)果是Stack。
}
var stackOfInts = Stack<Int>()
stackOfInts.append(10)
stackOfInts.append(20)
stackOfInts.append(30)
let suffix = stackOfInts.suffix(2)
// suffix 包含 20 和 30

在上面的例子中,SuffixStack 的關(guān)聯(lián)類型,也是 Stack ,所以 Stack 的后綴運算返回另一個 Stack 。另外,遵循 SuffixableContainer 的類型可以擁有一個與它自己不同的 Suffix 類型——也就是說后綴運算可以返回不同的類型。比如說,這里有一個非泛型 IntStack 類型的擴展,它遵循了 SuffixableContainer 協(xié)議,使用 Stack<Int> 作為它的后綴類型而不是 IntStack

extension IntStack: SuffixableContainer {
    func suffix(_ size: Int) -> Stack<Int> {
        var result = Stack<Int>()
        for index in (count-size)..<count {
            result.append(self[index])
        }
        return result
    }
    // 推斷 suffix 結(jié)果是 Stack<Int>。
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容