Associated Types 關(guān)聯(lián)類型

文中所描述的遵循協(xié)議的類型,之所以寫為類型是因為在Swift中遵循協(xié)議的可以是類,結(jié)構(gòu)體或者協(xié)議等等,官方文檔用type,所以文中寫為類型。

當(dāng)我們定義一個協(xié)議的時候,在里面聲明一個或多個關(guān)聯(lián)類型會很有幫助。關(guān)聯(lián)類型可以讓我們以占位符的形式在協(xié)議中定義一個類型。這個自定義的類型只有在該協(xié)議被遵循時才被特別指定。關(guān)聯(lián)類型的關(guān)鍵字是associatedtype

Associated Types基本使用

我們定義一個叫做Container的容器協(xié)議,里面聲明一個關(guān)聯(lián)類型叫Item:

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

這個容器協(xié)議定義了三個必須被實現(xiàn)的方法:

  • 這個容器可以通過append(_:)來添加數(shù)據(jù)
  • 通過count屬性我們可以獲取這個容器中數(shù)據(jù)的個數(shù)
  • 我們可以通過下標(biāo)來獲取這個容器中的每個數(shù)據(jù)

這個容器協(xié)議沒有特別指名其中的數(shù)據(jù)是如何被存儲的,以及具體的數(shù)據(jù)類型。這個容器協(xié)議只是特別定義了三個必須遵循的方法。只要能實現(xiàn)這三個方法,那么遵循協(xié)議了的類型也可以提供更多的其他方法。

任何遵循了這個容器協(xié)議的類型中必須指明這個Item是哪個類型。這里,可以通過特別指定來保證加入容器的每個對象的類型是正確的,同時也保證了當(dāng)用下標(biāo)取出的對象的類型也是正確的。

為了滿足這個需求,Container協(xié)議需要使用一種方式來指定容器中存儲的這些數(shù)據(jù)的類型,但該類型在協(xié)議中聲明是并不能指定為具體類型。而且在協(xié)議中的append(_:)方法中參數(shù)以及subscript(i: Int) -> Item返回值的類型必須一致。

為了達(dá)到這個目的,Container協(xié)議聲明了一個關(guān)聯(lián)類型Item,寫作associatedtype Item,這個協(xié)議并沒有指明Item是什么,這個將留給遵循該協(xié)議的類來指定。雖然如此,但這個Item指定了容器協(xié)議中每個數(shù)據(jù)的類型,從以上兩個方法可以看出,它保證了每個容器類型中的數(shù)據(jù)類型是一致的。

看一下遵循了該容器協(xié)議的具體類:

struct IntStack: Container {
    // original IntStack implementation
    var items = [Int]()
    mutating func push(_ item: Int) {
        items.append(item)
    }
    mutating func pop() -> Int {
        return items.removeLast()
    }
    // conformance to the Container protocol
    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類型實現(xiàn)了容器協(xié)議的所有需求。而且,IntStack也明確指定了Item其實就是Int類型。typealias Item = Int這句代碼就是將協(xié)議中的抽象類型變?yōu)榫唧w類型。

由于Swift的類型推斷,我們也不必特別指名Item是Int類型,因為IntStack遵循了容器協(xié)議,所以簡單的從append(_:)方法和subscript(i: Int) -> Item方法中可以推斷出Item就是Int,所以我們可以刪除typealias Item = Int這行代碼。

當(dāng)然,我們也可以將Stack做成范型,如下:

struct Stack<Element>: Container {
    // original Stack<Element> implementation
    var items = [Element]()
    mutating func push(_ item: Element) {
        items.append(item)
    }
    mutating func pop() -> Element {
        return items.removeLast()
    }
    // conformance to the Container protocol
    mutating func append(_ item: Element) {
        self.push(item)
    }
    var count: Int {
        return items.count
    }
    subscript(i: Int) -> Element {
        return items[i]
    }
}

現(xiàn)在,從append(_:)方法和subscript(i: Int) -> Item方法中可以看出使用的是Element,因此Swift可以推斷出關(guān)聯(lián)類型Item這回是Element。

擴(kuò)展一個指定類型來指定這個關(guān)聯(lián)類型

我們可以擴(kuò)展一個已存在的類型來遵循這個聲明了關(guān)聯(lián)類型的容器協(xié)議。
Swift的Array類型已經(jīng)提供了append(_:)方法,一個count屬性,以及通過下標(biāo)返回元素的方法。這也就是說我們可以擴(kuò)展Array類型使其遵循該容器協(xié)議。如下

extension Array: Container {}

Array中已存在的方法append(_:)和下標(biāo)返回方法使得Swift的類型判斷可以明確Item的類型,就和之前Stack一樣。這樣擴(kuò)展了Array之后,我們就可以把ArrayStack一起當(dāng)作Container來使用。

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

我們可以在協(xié)議中對關(guān)聯(lián)類型進(jìn)行類型約束,使得遵循該協(xié)議的類型同樣受到這個約束。如下,我們可以看到,遵循這個版本的Container的類型必須保證item是一樣的。

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

拿協(xié)議自身來作為協(xié)議內(nèi)關(guān)聯(lián)類型的約束

再從上面引深,我們可以將一個協(xié)議作為滿足自身需求來約束關(guān)聯(lián)類型。來看下下面這個例子,定義了一個遵循容器協(xié)議的協(xié)議,并添加了suffix(_:)方法。這個方法就是將從指定的下標(biāo)開始到末尾的數(shù)據(jù)裝到一個叫做Suffix的容器里并返回。

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

在這個協(xié)議里面,Suffix就和之前的Item一樣是關(guān)聯(lián)類型。
Suffix有兩個約束:

  • 必須遵循SuffixableContainer協(xié)議
  • 該協(xié)議內(nèi)的ItemContainer中的Item必須一致

接下來讓我們看下遵循了該協(xié)議的Stack是怎么寫的:

extension Stack: SuffixableContainer {
    func suffix(_ size: Int) -> Stack {
        var result = Stack()
        for index in (count-size)..<count {
            result.append(self[index])
        }
        return result
    }
    // Inferred that Suffix is Stack.
}
var stackOfInts = Stack<Int>()
stackOfInts.append(10)
stackOfInts.append(20)
stackOfInts.append(30)
let suffix = stackOfInts.suffix(2)
// suffix contains 20 and 30

從上面的例子可以看出,StackSuffix關(guān)聯(lián)類型就是Stack自身(別忘了,Stack是個范型哦),所以suffix(_:)操作將返回另一個Stack。然而,我們也可以在遵循了SuffixableContainer協(xié)議的同時使Suffix關(guān)聯(lián)類型不是自身,也就是說,我們可以在suffix(_:)方法返回一個不同的類型,來看下下面這個例子,我們用一個非范型類型IntStack來遵循SuffixableContainer協(xié)議,但是我們把關(guān)聯(lián)類型IntStack改為范型Stack<Int>

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
    }
    // Inferred that Suffix is 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ù)。

相關(guān)閱讀更多精彩內(nèi)容

  • importUIKit classViewController:UITabBarController{ enumD...
    明哥_Young閱讀 4,200評論 1 10
  • 泛型(Generics) 泛型代碼允許你定義適用于任何類型的,符合你設(shè)置的要求的,靈活且可重用的 函數(shù)和類型。泛型...
    果啤閱讀 760評論 0 0
  • 136.泛型 泛型代碼讓你可以寫出靈活,可重用的函數(shù)和類型,它們可以使用任何類型,受你定義的需求的約束。你可以寫出...
    無灃閱讀 1,658評論 0 4
  • 01 轉(zhuǎn)眼就周五了,又一次感慨時間飛逝。翻開這一周的時間記錄,除了日更和運(yùn)動的每天準(zhǔn)時,其他都一無是處。 定了目標(biāo)...
    麥花魔法花園閱讀 181評論 0 0
  • 生日。我愛這兩個字。 我無比的迷戀自己的生日,我喜歡她,那個對于我來說最特殊的日子。 我想念曾經(jīng)擁有的種種美好。 ...
    符安閱讀 564評論 0 1

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