Swift学习-面向协议编程中associatedtype的认识

https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Generics.html

在定义协议时,有时将一个或多个关联类型声明为协议定义的一部分是有用的。一个相关联的类型给出了一个占位符名称到被用作协议的一部分的类型。在采用协议之前,不会指定用于该关联类型的实际类型。关联类型用associatedtype关键字指定。

这里是一个叫做协议的例子Container,它声明一个关联的类型Item

1
2
3
4
5
6
protocol Container {
associatedtype Item
mutating func append(_ item: Item)
var count: Int { get }
subscript(i: Int) -> Item { get }
}

Container协议定义了三种所需的功能,任何容器必须提供:

  • 必须使用append(_:)方法向容器添加一个新项目。
  • 必须可以通过count返回Int值的属性访问容器中的项目计数。
  • 必须可以使用具有Int索引值的下标来检索容器中的每个项目。

该协议不指定应该如何存储容器中的项目或者允许的类型。该协议仅指定任何类型必须提供的功能的三位,以便被认为是Container。只要满足这三个要求,一致的类型可以提供附加的功能。

符合Container协议的任何类型必须能够指定其存储的值的类型。具体来说,它必须确保仅将正确类型的项目添加到容器中,并且必须清楚其下标返回的项目的类型。

要定义这些要求,Container协议需要一种方法来引用容器将要容纳的元素的类型,而不需要知道特定容器的类型。该Container协议需要指定传递给任何值append(_:)方法必须具有相同的类型容器的元件的类型,以及通过所述容器的下标所返回的值将是相同的类型容器的元件的类型。

为了实现这一点,该Container协议声明称为关联的类型Item,写为associatedtype Item 该协议没有定义什么Item是 - 为任何符合类型提供的信息。尽管如此,Item别名提供了一种方法来引用a中的项目的类型Container,并定义与append(_:)方法和下标一起使用的类型,以确保Container强制执行any的预期行为。

这是一个非常IntStack类型的版本,适用于符合Container协议:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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类型实现了Container协议的所有三个要求,并且在每种情况下都包含了IntStack类型现有功能的一部分以满足这些要求。

此外,IntStack规定了对于这种实现Container,适合Item使用的是一种Int。将该typealias Item = Int抽象类型的定义Item转化为具体类型Int的该Container协议的实现。

感谢Swift的类型推断,实际上并不需要声明具体ItemInt定义IntStack。由于IntStack符合Container协议的所有要求,所以Swift可以Item通过查看append(_:)方法参数的item类型和下标的返回类型来推断适用的方法。实际上,如果你typealias Item = Int从上面的代码中删除一行,那么所有内容都仍然有效,因为它应该是什么类型的Item

你也可以使通用Stack类型符合Container协议:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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]
}
}

此时,type参数Element用作append(_:)方法参数的item类型和下标的返回类型。因此,Swift可以推断出这Element是特定容器使用的适当类型Item

扩展现有类型以指定关联类型

你可以扩展现有类型以添加​​符合协议,如添加协议与扩展一致性所述。这包括具有关联类型的协议。

Swift的Array类型已经提供了一个append(_:)方法,一个count属性和一个带Int索引的下标来检索它的元素。这三个功能符合Container协议的要求。这意味着你可以通过声明采用协议来扩展Array以符合协议。你可以使用空的扩展名来执行此操作,如使用扩展名声明协议采用所述: ContainerArray

1
extension Array: Container {}

Array的现有append(_:)方法和下标使Swift能够推断Item适用于的Stack类型,就像上面的泛型一样。定义此扩展名后,你可以使用任何Array作为Container