Swift關聯類型
關聯類型
當定義一個協議時,有的時候聲明一個或多個關聯類型作爲協議定義的一部分是非常有用的。一個關聯類型給定作用於協議部分的類型一個節點名(或別名)。作用於關聯類型上實際類型是不需要指定的,直到該協議接受。關聯類型被指定爲typealias
關鍵字。
關聯類型行爲
這裏是一個Container
協議的例子,定義了一個ItemType關聯類型:
protocol Container {
typealias ItemType
mutating func append(item: ItemType)
var count: Int { get }
subscript(i: Int) -> ItemType { get }
}
Container
協議定義了三個任何容器必須支持的兼容要求:
- 必須可能通過
append
方法添加一個新item到容器裏; - 必須可能通過使用
count
屬性獲取容器裏items的數量,並返回一個Int
值; - 必須可能通過容器的
Int
索引值下標可以檢索到每一個item。
這個協議沒有指定容器裏item是如何存儲的或何種類型是允許的。這個協議只指定三個任何遵循Container
類型所必須支持的功能點。一個遵循的類型也可以提供其他額外的功能,只要滿足這三個條件。
任何遵循Container
協議的類型必須指定存儲在其裏面的值類型,必須保證只有正確類型的items可以加進容器裏,必須明確可以通過其下標返回item類型。
爲了定義這三個條件,Container
協議需要一個方法指定容器裏的元素將會保留,而不需要知道特定容器的類型。Container
協議需要指定任何通過append
方法添加到容器裏的值和容器裏元素是相同類型,並且通過容器下標返回的容器元素類型的值的類型是相同類型。
爲了達到此目的,Container
協議聲明瞭一個ItemType的關聯類型,寫作typealias ItemType
。這個協議不會定義ItemType
是什麼的別名,這個信息留給了任何遵循協議的類型來提供。儘管如此,ItemType
別名支持一種方法識別在一個容器裏的items類型,以及定義一種使用在append
方法和下標中的類型,以便保證任何期望的Container
的行爲是強制性的。
這裏是一個早前IntStack類型的非泛型版本,適用於遵循Container協議:
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 ItemType = 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
的實現,適用的ItemType被用作Int
類型。對於這個Container
協議實現而言,定義 typealias ItemType = Int
,將抽象的ItemType
類型轉換爲具體的Int
類型。
感謝Swift類型參考,你不用在IntStack
定義部分聲明一個具體的Int
的ItemType
。由於IntStack
遵循Container
協議的所有要求,只要通過簡單的查找append
方法的item參數類型和下標返回的類型,Swift就可以推斷出合適的ItemType
來使用。確實,如果上面的代碼中你刪除了typealias ItemType = Int
這一行,一切仍舊可以工作,因爲它清楚的知道ItemType使用的是何種類型。
你也可以生成遵循Container
協議的泛型Stack
類型:
struct Stack<T>: Container {
// original Stack<T> implementation
var items = T[]()
mutating func push(item: T) {
items.append(item)
}
mutating func pop() -> T {
return items.removeLast()
}
// conformance to the Container protocol
mutating func append(item: T) {
self.push(item)
}
var count: Int {
return items.count
}
subscript(i: Int) -> T {
return items[i]
}
}
這個時候,佔位類型參數T
被用作append
方法的item參數和下標的返回類型。Swift 因此可以推斷出被用作這個特定容器的ItemType
的T
的合適類型。
擴展一個存在的類型爲一指定關聯類型
在使用擴展來添加協議兼容性中有描述擴展一個存在的類型添加遵循一個協議。這個類型包含一個關聯類型的協議。
Swift的Array
已經提供append
方法,一個count
屬性和通過下標來查找一個自己的元素。這三個功能都達到Container
協議的要求。也就意味着你可以擴展Array
去遵循Container
協議,只要通過簡單聲明Array
適用於該協議而已。如何實踐這樣一個空擴展,在使用擴展來聲明協議的採納中有描述這樣一個實現一個空擴展的行爲:
extension Array: Container {}
如同上面的泛型Stack
類型一樣,Array的append
方法和下標保證Swift
可以推斷出ItemType
所使用的適用的類型。定義了這個擴展後,你可以將任何Array
當作Container
來使用。