
本文參考自蘋果官方文檔Generics
泛型(Generics)
泛型代碼允許你定義適用于任何類型的,符合你設(shè)置的要求的,靈活且可重用的 函數(shù)和類型。
泛型代碼讓你避免重復(fù)的代碼,并且用一種清晰和抽象的方式來(lái)表達(dá)其意圖。
泛型是 Swift 強(qiáng)大特征中的其中一個(gè)
Swift標(biāo)準(zhǔn)庫(kù)中的很多內(nèi)容都是通過(guò)泛型代碼構(gòu)建出來(lái)的。
事實(shí)上,泛型的使用貫穿了整本語(yǔ)言手冊(cè)(The Swift Programming Language (Swift 3)),只是你沒(méi)有發(fā)現(xiàn)而已。
例如,Swift 的數(shù)組和字典類型都是泛型集合。
你可以創(chuàng)建一個(gè)Int數(shù)組,也可創(chuàng)建一個(gè)String數(shù)組,或者甚至于可以是任何其他Swift的類型數(shù)據(jù)數(shù)組。
同樣的,你也可以創(chuàng)建元素是任何指定類型的字典(dictionary).
泛型所解決的問(wèn)題
這里是一個(gè)標(biāo)準(zhǔn)的,非泛型函數(shù)swapTwoInts,用來(lái)交換兩個(gè)Int值:
<pre>
<code>
func swapTwoInts(inout a: Int, inout _ b: Int) { let temporaryA = a a = b b = temporaryA }
</code>
</pre>
這個(gè)函數(shù)使用輸入輸出參數(shù)(inout)來(lái)交換a和b的值,請(qǐng)參考輸入輸出參數(shù)。
swapTwoInts函數(shù)可以交換b的原始值到a,也可以交換a的原始值到b,你可以調(diào)用這個(gè)函數(shù)交換兩個(gè)Int變量值:
<pre>
<code>
var someInt = 3 var anotherInt = 107 swapTwoInts(&someInt, &anotherInt) print("someInt is now \(someInt), and anotherInt is now \(anotherInt)") //someInt is now 107, and anotherInt is now 3
</code>
</pre>
swapTwoInts函數(shù)是非常有用的,但是它只能交換Int值,如果想要交換兩個(gè)String或者Double,就不得不寫更多的函數(shù),如swapTwoStrings和swapTwoDoubles,如下所示:
<pre>
<code>
func swapTwoStrings(inout a: String, inout _ b: String) { let temporaryA = a a = b b = temporaryA } func swapTwoDoubles(inout a: Double, inout _ b: Double) { let temporaryA = a a = b b = temporaryA }
</code>
</pre>
你可能注意到swapTwoInts,swapTwoStrings和swapTwoDoubles函數(shù)功能都是相同的,唯一不同之處就是參數(shù)類型不同,分別是Int,String和Double。
此時(shí)就需要某個(gè)更有用更靈活的單個(gè)函數(shù)用來(lái)交換兩個(gè)任何類型的值的,很幸運(yùn)的是,泛型代碼幫你解決了這種問(wèn)題。
泛型函數(shù)
泛型函數(shù)可以應(yīng)用于任何類型
這里是一個(gè)上面swapTwoInts函數(shù)的泛型版本
<pre>
<code>
func swapTwoValues<T>(inout a: T, inout _ b: T) { let temporaryA = a a = b b = temporaryA }
</code>
</pre>
swapTwoValues和swapTwoInts函數(shù)的主體是一樣的,只在第一行有一點(diǎn)不同:
<pre>
<code>
func swapTwoInts(inout a: Int, inout _ b: Int) func swapTwoValues<T>(inout a: T, inout _ b: T)
</code>
</pre>
這個(gè)函數(shù)的泛型版本使用了占位類型(通常用字母T來(lái)表示)來(lái)代替實(shí)際類型名稱(如Int,String或Double)。
占位類型沒(méi)有表明T必須是什么類型,但是它表明了a和b必須是同一類型T,而不管T表示什么類型。
T所代表的實(shí)際類型只有swapTwoValues函數(shù)在每次被調(diào)用時(shí)才能決定。
另外一個(gè)不同之處在于這個(gè)泛型函數(shù)名后的占位類型T是用尖括號(hào)<>括起來(lái)的。
這個(gè)<>表示T是swapTwoValues函數(shù)中的一個(gè)占位類型。
因?yàn)門是一個(gè)占位類型,Swift不會(huì)去查找命名為T的實(shí)際類型。
swapTwoValues函數(shù)也可以被調(diào)用。
每次swapTwoValues被調(diào)用,T所代表的類型都會(huì)傳給函數(shù)。
在下面的兩個(gè)例子中,T分別代表Int和String:
<pre>
<code>
`
var someInt = 3
var anotherInt = 107
swapTwoValues(&someInt, &anotherInt)
print(someInt,anotherInt)
var someString = "hello"
var anotherString = "world"
swapTwoValues(&someString, &anotherString)
print(someString,anotherString)
//107 3
//world hello
`
</code>
</pre>
注意
上面定義的函數(shù)swapTwoValues是受swap函數(shù)的啟發(fā)。
swap函數(shù)存在于Swift標(biāo)準(zhǔn)庫(kù).如果你在自己代碼中需要類似swapTwoValues函數(shù)的功能,你不用自己來(lái)實(shí)現(xiàn)因?yàn)榭梢允褂靡汛嬖诘膕wap函數(shù)
類型參數(shù)
在上面的swapTwoValues例子中,占位類型T是類型參數(shù)的一個(gè)示例。
類型參數(shù):
指定并命名了一個(gè)占位類型
寫在函數(shù)名之后
寫在一對(duì)<>中,例如<T>
一旦類型參數(shù)被指定(指的是:<T>)
你可以使用該類型參數(shù):
1.作為函數(shù)的參數(shù)類型
2.作為函數(shù)返回類型
3.作為函數(shù)主體中的類型標(biāo)注。
以swapTwoValues函數(shù)為例來(lái)體會(huì):
<pre><code>func swapTwoValues <T>//指定類型參數(shù)為占位類型T (inout a: T, inout _ b: T)//定義函數(shù)的參數(shù)類型為類型參數(shù)T { let temporaryA = a a = b b = temporaryA }</code></pre>
類型參數(shù)在函數(shù)被調(diào)用時(shí)都會(huì)被實(shí)際類型所替換
在之前的swapTwoValues例子中,當(dāng)函數(shù)第一次被調(diào)用時(shí),T被Int替換,第二次調(diào)用時(shí),T被String替換。(再寫一遍之前的例子)
<pre>
<code>
`
var someInt = 3
var anotherInt = 107
swapTwoValues(&someInt, &anotherInt)
print(someInt,anotherInt)
var someString = "hello"
var anotherString = "world"
swapTwoValues(&someString, &anotherString)
print(someString,anotherString)
//107 3
//world hello
`
</code>
</pre>
你可以提供多個(gè)類型參數(shù)通過(guò)編寫多個(gè)類型參數(shù)并在尖括號(hào)中用逗號(hào)分開(kāi)的方式
eg.
<pre><code><T,U></code></pre>
命名類型參數(shù)
在大多數(shù)的情況下,類型闡述具有描述性的名稱,例如Dictionary<Key,Value>中Key,Value和Array<Element>中的Element,來(lái)告訴讀者泛型類型/泛型函數(shù)與其中的類型參數(shù)的關(guān)系.
然而,當(dāng)用當(dāng)二者之間的關(guān)系沒(méi)有具體意義時(shí),可以用傳統(tǒng)的方法來(lái)命名類型參數(shù),使用單個(gè)字母如T,U,V.
泛型函數(shù)或泛型類型 需要指定一個(gè)占位類型(如上面的泛型函數(shù),或一個(gè)存儲(chǔ)單一類型的泛型集,如數(shù)組)
通常用一單個(gè)字母T來(lái)命名類型參數(shù)。
注意
最好用大寫字母開(kāi)頭的駝峰式命名法(例如T和MyTypeParameter)來(lái)給類型參數(shù)命名,以表明它們是類型的占位符, 而非類型的值。
泛型類型
除了定義泛型函數(shù),Swift允許你自定義泛型類型.
這些自定義的類,結(jié)構(gòu)體和枚舉 如同Array和Dictionary ,可以與任何類型一起工作.
這部分向你展示如何寫一個(gè)泛型集合類型Stack。
Stack是一系列有序的值的集合,和Array類似,但比Array有更多限制:
數(shù)組可以允許其里面任何位置的插入/刪除操作
棧只允許在集合的末端添加新的項(xiàng)(如同push一個(gè)新值進(jìn)棧)。同樣的棧也只能從末端移除項(xiàng)(如同pop一個(gè)值出棧)。
注意
棧的概念已經(jīng)被UINavigationController類使用來(lái)模擬視圖控制器的導(dǎo)航結(jié)構(gòu).
通過(guò)調(diào)用UINavigationController的pushViewController方法來(lái)為導(dǎo)航棧添加新的視圖控制器;通過(guò)popViewController方法來(lái)從導(dǎo)航棧中移除試圖控制器.
棧是一種非常有用的集合模型當(dāng)你需要嚴(yán)格的后進(jìn)先出的方式來(lái)管理集合時(shí).
下圖展示了一個(gè)棧的壓棧(push)/出棧(pop)的行為:

- 現(xiàn)在有三個(gè)值在棧中
- 第四個(gè)值“pushed”到棧的頂部
- 現(xiàn)在有四個(gè)值在棧中,最近壓入的那個(gè)在頂部
- 棧中最頂部的那個(gè)項(xiàng)被移除,或稱之為“popped”
- 移除掉一個(gè)值后,現(xiàn)在棧又重新只有三個(gè)值。
The Swift Programming Language 中文版
這里展示了如何寫一個(gè)非泛型版本的棧,Int值型的棧
<pre>
<code>
`
struct IntStack
{
var items = Int
mutating func push(item: Int)
{
items.append(item)
}
mutating func pop() -> Int
{
return items.removeLast()
}
}
`
</code>
</pre>
這個(gè)結(jié)構(gòu)體在棧中使用一個(gè)Array類型的items變量來(lái)存儲(chǔ)值。
Stack提供兩個(gè)方法:push和pop,向棧中壓進(jìn)一個(gè)值和從棧中移除一個(gè)值。
這些方法標(biāo)記為mutating(可變的),因?yàn)樗鼈冃枰薷?或轉(zhuǎn)換)結(jié)構(gòu)體IntStack中的屬性items。
上面所展現(xiàn)的IntStack類型只能用于Int值,如果定義一個(gè)泛型的Stack(可以處理任何類型值的棧)將會(huì)是是非常有用的。
這里是一個(gè)IntStack的泛型版本:
<pre><code>
struct Stack<Element> { var items = [Element]() mutating func push(item:Element) { items.append(item) } mutating func pop()->Element { return items.removeLast() } }
</code></pre>
泛型版本的Stack和非泛型版本的IntStack基本相同
只是泛型版本Stack的類型參數(shù)Element代替了實(shí)際Int類型.
類型參數(shù)Element包含在一對(duì)尖括號(hào)里<Element>,緊隨在結(jié)構(gòu)體名字后面。
Element為"某種類型Element"定義了一個(gè)占位符名稱以便在之后能被使用.
這種之后會(huì)被用到的類型在結(jié)構(gòu)體的定義的任何地方來(lái)表示Element類型。
在此示例中, Element在如下三個(gè)地方被用作占位符:
1.創(chuàng)建一個(gè)名為items的屬性,使用空的類型為[Element]的數(shù)組對(duì)其進(jìn)行初始化
var items = [Element]()
2.定義參數(shù)名為item的push(_:) 方法,參數(shù)item必須是Element類型
mutating func push(item: Element) { items.append(item) }
3.定義pop方法,該犯法返回值是一個(gè)Element類型的值。
mutating func pop() -> Element { return items.removeLast() }
由于Stack是泛型類型,所以在Swift中其可以用來(lái)創(chuàng)建存儲(chǔ)任何有效的類型的值的棧,這種方式如同Array和Dictionary可以存儲(chǔ)任何類型的元素.
可以通過(guò)泛型類型名 + <實(shí)際類型>的方式來(lái)創(chuàng)建并初始化一個(gè)泛型實(shí)例。
eg:
<pre><code>var stackOfStrings = Stack<String>() //在尖括號(hào)里寫出棧中需要存儲(chǔ)的數(shù)據(jù)類型</code></pre>
此時(shí)棧stackOfStrings是空的
向stackOfStrings壓入4個(gè)字符串
<pre><code>stackOfStrings.push("uno") stackOfStrings.push("dos") stackOfStrings.push("tres") stackOfStrings.push("cuatro")</code></pre>

從stackOfStrings中移除"cuatro"
<pre><code>let fromTheTop = stackOfStrings.pop() //從棧中移除"cuatro",并將方法的返回值"cuatro"存儲(chǔ)在fromTheTop中</code></pre>

擴(kuò)展一個(gè)泛型類型
當(dāng)你擴(kuò)展一個(gè)泛型類型的時(shí)候,你不用在擴(kuò)展的定義中提供類型參數(shù)的列表。
因?yàn)?strong>原始類型定義中聲明的類型參數(shù)的列表在擴(kuò)展體里是可以使用的
下面的例子擴(kuò)展了泛型Stack類型,為其添加了一個(gè)名為topItem的只讀計(jì)算屬性,它將會(huì)返回當(dāng)前棧頂端的元素而不會(huì)將其從棧中移除。
<pre>
<code>
extension Stack { var topItem: Element? { return items.isEmpty ? nil : items[items.count - 1] } }
</code>
</pre>
topItem屬性會(huì)返回一個(gè)Element類型的可選值。
當(dāng)棧為空的時(shí)候,topItem將會(huì)返回nil
當(dāng)棧不為空的時(shí)候,topItem會(huì)返回items數(shù)組中的最后一個(gè)元素
注意
這里的擴(kuò)展并沒(méi)有定義一個(gè)類型參數(shù)列表。
而Stack類型已有的類型參數(shù)名稱(Element)被用在擴(kuò)展中topItem計(jì)算屬性的可選類型。
topItem計(jì)算屬性現(xiàn)在可以被用來(lái)返回任意 Stack 實(shí)例的頂端元素而無(wú)需移除它:
<pre><code>if let topItem = stackOfStrings.topItem { print("The top item on the stack is \(topItem).") } //The top item on the stack is tres.</code></pre>
類型約束
泛型函數(shù)swapTwoValues和泛型類型Stack可以與類型一起工作
不過(guò),有的時(shí)候 對(duì) 與泛型函數(shù)和泛型類型 一起工作的 類型 進(jìn)行 類型約束 很有用.
類型約束 指定了 類型參數(shù) 必須 繼承自一個(gè)指定的類 或者 遵循一個(gè)特定的協(xié)議或一個(gè)合成的協(xié)議。
例如,Swift的Dictionary類型對(duì)類型參數(shù)Key的類型做了些限制。
在Dictionary的描述中, Dictionary的類型參數(shù)Key必須是可哈希的,也就是說(shuō),必須有一種方法可以使其被唯一的表示。
Dictionary之所以其類型參數(shù)Key是可哈希是為了以便于其檢查否已經(jīng)包含某個(gè)特定鍵的值。
如無(wú)此需求,Dictionary既無(wú)法表述是否插入或者替換了某個(gè)特定Key對(duì)應(yīng)的Value,也無(wú)法查找到已經(jīng)存儲(chǔ)在Dictionary里面的給定Key對(duì)應(yīng)的Value。
這個(gè)需求通過(guò)在Dictionary的Key上強(qiáng)制加上一個(gè)類型約束來(lái)實(shí)現(xiàn),指定Dictionary的Key的類型必須遵循Hashable協(xié)議(Swift標(biāo)準(zhǔn)庫(kù)中定義的一個(gè)特定協(xié)議)
所有的Swift基本類型(如String,Int,Double和Bool)默認(rèn)都是可哈希的。
當(dāng)你自定義泛型類型時(shí),你可以自定義類型約束,并且這些約束要提供泛型編程的大部分的能力。
類型約束語(yǔ)法
你可以設(shè)置類型約束,通過(guò) 將一個(gè)類或協(xié)議放在類型參數(shù)之后 ,通過(guò) 冒號(hào) 分隔,來(lái) 作為類型參數(shù)列表的一部分。
這種作用于泛型函數(shù)/泛型類型的類型約束的基礎(chǔ)語(yǔ)法如下所示(以泛型類型為例):
<pre>
<code>
`
//注:
//<T: SomeClass, U: SomeProtocol>//類型約束
//(someT: T, someU: U)//泛型函數(shù)的參數(shù)的類型分別是T,U
func someFunction<T: SomeClass, U: SomeProtocol>
(someT: T, someU: U)
{
// 這里是函數(shù)主體
}
`
</code>
</pre>
上面示例有兩個(gè)類型參數(shù)。
第一個(gè)類型參數(shù)T,有一個(gè)要求T是SomeClass子類的類型約束
第二個(gè)類型參數(shù)U,有一個(gè)要求U遵循SomeProtocol協(xié)議的類型約束。
類型約束實(shí)例
這里有個(gè)名為findStringIndex的非泛型函數(shù),該函數(shù)功能是查找一給定String在數(shù)組中的索引值。若查找到匹配的字符串, findStringIndex函數(shù)返回該字符串在數(shù)組中的索引值,反之則返回nil
<pre><code>func findStringIndex(array: [String], _ valueToFind: String) -> Int? { for (index, value) in array.enumerate() { if value == valueToFind { return index } } return nil }</code></pre>
findStringIndex函數(shù)可以作用于查找一字符串?dāng)?shù)組中的某個(gè)字符串:
<pre><code>let strings = ["cat", "dog", "llama", "parakeet", "terrapin"] var foundIndex = findStringIndex(strings, "llama") print("The index of llama is \(foundIndex)") foundIndex = findStringIndex(strings, "不存在") print("The index of llama is \(foundIndex)") //The index of llama is Optional(2) //The index of llama is nil</code></pre>
如果只是能對(duì)字符串查找在數(shù)組中的的索引,用處不是很大
不過(guò),你可以寫出相同功能的泛型函數(shù)findIndex,用類型T替換String類型。
這里展示了findStringIndex的泛型版本findIndex。
注意:這個(gè)函數(shù)返回值類型仍為Int?因?yàn)楹瘮?shù)返回的是一個(gè)可選的索引數(shù),而不是從數(shù)組中得到的一個(gè)可選值。
注意:這個(gè)函數(shù)被不會(huì)編譯,原因在例子后面會(huì)說(shuō)明:
<pre><code>func findIndex<T>(array: [T], _ valueToFind: T) -> Int? { for (index, value) in array.enumerate() { if value == valueToFind { return index } } return nil }</code></pre>
上面所寫的函數(shù)不會(huì)被編譯。問(wèn)題出現(xiàn)在等式的檢查上, “if value == valueToFind” 。
不是所有的Swift中的類型都可以用等式符(==)進(jìn)行比較
如果你自定義了一個(gè)類或結(jié)構(gòu)體來(lái)表示一個(gè)復(fù)雜的數(shù)據(jù)模型,Swift是猜測(cè)不出 "=="對(duì)于你自定義的類或結(jié)構(gòu)體所表示的含義 .
嘗試編譯這個(gè)函數(shù)時(shí)Xcode會(huì)報(bào)錯(cuò):
//Binary operator '==' cannot be applied to two 'T' operands
//二進(jìn)制運(yùn)算符'=='不能應(yīng)用于兩個(gè)'T'類型的操作數(shù)
但是,Swift標(biāo)準(zhǔn)庫(kù)中定義了一個(gè)Equatable協(xié)議
Equatable協(xié)議要求任何遵循Equatable協(xié)議的類型需要實(shí)現(xiàn)(==)和(!=)操作符來(lái)對(duì)任意兩個(gè)該類型的實(shí)例進(jìn)行比較。
Swift所有的標(biāo)準(zhǔn)的類型都自動(dòng)支持Equatable協(xié)議。
在下面的例子中,任何遵守Equatable協(xié)議的類型都可以在findIndex函數(shù)中安全使用,因?yàn)槠浔WC支持(==)和(!=)操作符。
為了說(shuō)明這個(gè)事實(shí),當(dāng)你定義一個(gè)函數(shù)時(shí),你可以將類型約束Equatable作為類型參數(shù)定義的一部分:
<pre><code>func findIndex<T: Equatable>(array: [T], _ valueToFind: T) -> Int? { for (index, value) in array.enumerate() { if value == valueToFind { return index } } return nil }</code></pre>
函數(shù)findIndex的類型參數(shù)設(shè)置為T: Equatable ,也就意味著“類型T遵循Equatable協(xié)議”。
findIndex函數(shù)現(xiàn)在則可以成功的編譯過(guò),并且作用于任何遵循Equatable的類型,如Double或String :
<pre><code>let doubleIndex = findIndex([3.14159, 0.1, 0.25], 9.3) print(doubleIndex) let stringIndex = findIndex(["Mike", "Malcolm", "Andrea"], "Andrea") print(stringIndex) //nil //Optional(2)</code></pre>
關(guān)聯(lián)類型(Associated Types)
有時(shí)在協(xié)議的定義中
聲明一或多個(gè)關(guān)聯(lián)類型作為協(xié)議定義的一部分非常有用
關(guān)聯(lián)類型將一個(gè)類型命名為某占位類型,使該類型能夠作為協(xié)議的一部分
關(guān)聯(lián)類型代表的實(shí)際類型在協(xié)議被實(shí)現(xiàn)前是不需要指定的。
使用 associatedtype + 占位類型名 來(lái) 指定一個(gè)關(guān)聯(lián)類型。
(個(gè)人理解:關(guān)聯(lián)類型就是占位類型,但是關(guān)聯(lián)類型通常和協(xié)議一起使用)
關(guān)聯(lián)類型實(shí)例
這里是一個(gè)Container協(xié)議的例子,定義了一個(gè)ItemType關(guān)聯(lián)類型:
<pre><code>`
protocol Container
{
//使用associatedtype聲明關(guān)聯(lián)類型ItemType
associatedtype ItemType
//功能1:向容器添加新元素
mutating func append(item: ItemType)
//功能2:容器中元素個(gè)數(shù)的屬性
var count: Int { get }
//功能3:下標(biāo)
subscript(i: Int) -> ItemType { get }
}
`</code></pre>
Container協(xié)議定義了任何容器必須支持的3個(gè)功能:
1.必須可以通過(guò)append方法添加新元素到容器里
2.必須可以通過(guò)使用count屬性獲取容器里元素的數(shù)量,并返回一個(gè)Int值
3.必須可以通過(guò)容器的Int類型的下標(biāo)能夠檢測(cè)到每一個(gè)元素
這個(gè)協(xié)議沒(méi)有指定容器里的元素是如何存儲(chǔ)的,沒(méi)有指定元素屬于何種類型。
這個(gè)協(xié)議只指定了3個(gè)任何遵循Container協(xié)議的類型所必須支持的功能。
一個(gè)遵循Container協(xié)議的類型在滿足這三個(gè)條件的情況下*也可以提供其他額外的功能。
任何遵循Container協(xié)議的類型
1.必須指定存儲(chǔ)在其里面的值的類型
mutating func append(item: ItemType)
2必須保證只有正確類型的元素可以加進(jìn)容器里
var count: Int { get }
3.必須明確可以通過(guò)其下標(biāo)返回元素類型。
subscript(i: Int) -> ItemType { get }
為了定義上述3個(gè)規(guī)定,Container協(xié)議需要 通過(guò)某種方式(也就是聲明一個(gè)關(guān)聯(lián)類型) 表示容器里存儲(chǔ)的元素的類型,而不需要知道特定的容器的類型。
Container協(xié)議 需要表明 append方法的參數(shù)的類型/下標(biāo)返回值的類型/容器中存儲(chǔ)的元素的類型 是相同的
因此 Container協(xié)議 聲明了 一個(gè) 名為ItemType的關(guān)聯(lián)類型,寫作associatedtype ItemType
雖然 Container協(xié)議 不會(huì)定義ItemType是什么類型,因?yàn)?ItemType代表的實(shí)際類型 將 由遵循Container協(xié)議的類型來(lái)提供。
但是,ItemType這個(gè)別名提供了一種方法來(lái):
表示Container協(xié)議中的元素的類型
定義一種類型用于append方法和subscript
以確保任意一個(gè)Container的預(yù)期行為(Container協(xié)議中的3個(gè)規(guī)定)能夠執(zhí)行.
這里是之前的非泛型版本的IntStack類型的定義,現(xiàn)在遵循Container協(xié)議:
<pre><code>`
struct IntStack: Container//聲明遵循Container協(xié)議
{
//IntStack的原始實(shí)現(xiàn)
var items = Int
mutating func push(item: Int)
{
items.append(item)
}
mutating func pop() -> Int
{
return items.removeLast()
}
//實(shí)現(xiàn)Container協(xié)議的規(guī)定
//表示ItemType是Int的別名
//由于Swift類型推斷功能的存在,這一行不寫也可以推斷出ItemType的實(shí)際類型是Int
typealias ItemType = Int
mutating func append(item: Int)
{
self.push(item)
}
var count: Int
{
return items.count
}
subscript(i: Int) -> Int
{
return items[i]
}
}
`</code></pre>
IntStack類型實(shí)現(xiàn)了Container協(xié)議的所有三個(gè)要求
此外,在IntStack類型對(duì)Container協(xié)議的實(shí)現(xiàn)中ItemType的實(shí)際類型是Int類型,typealias ItemType = Int這個(gè)定義把抽象的ItemType類型轉(zhuǎn)換為具體的Int類型。
由于Swift類型推斷功能,你不用在IntStack的定義中將占位類型ItemType聲明為實(shí)際的Int類型。
由于IntStack類型實(shí)現(xiàn)了Container協(xié)議的所有要求,Swift可以推斷出占位類型ItemType的實(shí)際類型來(lái)使用。
事實(shí)上,如果上面的代碼中你刪除了typealias ItemType = Int這一行,編譯時(shí)仍然可以成功,因?yàn)镮temType是哪一種類型一目了然。
eg.
<pre><code>`
var intStack = IntStack()
intStack.push(111)
intStack.push(222)
intStack.push(333)
intStack.push(444)
print("intStack.count->(intStack.count)")
let poped = intStack.pop()
print("poped->(poped)")
for index in 0..<intStack.count
{
print("(index)->(intStack[index])")
}
//intStack.count->4
//poped->444
//0->111
//1->222
//2->333
`</code></pre>
你也可以定義遵循Container協(xié)議的泛型類型Stack:
<pre><code>struct Stack<Element>: Container { //Stack的原始實(shí)現(xiàn) var items = [Element]() mutating func push(item: Element) { items.append(item) } mutating func pop() -> Element { return items.removeLast() } //遵循Container協(xié)議 mutating func append(item: Element) { self.push(item) } var count: Int { return items.count } subscript(i: Int) -> Element { return items[i] } }</code></pre>
這個(gè)時(shí)候,占位類型Element被用作Container協(xié)議中規(guī)定的append方法中item參數(shù)的類型和下標(biāo)的返回類型。
Swift因此可以推斷出Element類型就是這個(gè)特定容器的ItemType類型
在擴(kuò)展中使已存在的類型遵循協(xié)議
在 在擴(kuò)展中使已存在的類型遵循協(xié)議 中有描述通過(guò)擴(kuò)展使一個(gè)存在的類型遵循一個(gè)協(xié)議(該協(xié)議中可能有關(guān)聯(lián)類型)。
Swift的Array已經(jīng)提供append方法,count屬性和下標(biāo)來(lái)查找元素。
這三個(gè)功能都實(shí)現(xiàn)了Container協(xié)議的要求。
也就意味著你可以通過(guò)擴(kuò)展Array使其遵循Container協(xié)議,只要通過(guò)簡(jiǎn)單聲明Array遵循該協(xié)議。
在通過(guò)擴(kuò)展補(bǔ)充聲明遵循協(xié)議中有描述可以通過(guò)一個(gè)空擴(kuò)展來(lái)實(shí)現(xiàn)此目的:
eg.
<pre><code>extension Array: Container {}</code></pre>
如同上面的泛型類型Stack一樣,Array的append方法和下標(biāo)保證Swift可以推斷出Container協(xié)議中的ItemType類型所指代的實(shí)際類型。
定義了這個(gè)擴(kuò)展后,你可以將任何Array當(dāng)作Container來(lái)使用。
Where語(yǔ)句
類型約束 讓你能夠?yàn)?與泛型函數(shù)/泛型類型相關(guān)聯(lián)的類型參數(shù) 設(shè)置要求
類型約束 在 為關(guān)聯(lián)類型定義要求 方面 也非常有用。
你可以通過(guò)定義where語(yǔ)句 作為類型參數(shù)列表的一部分來(lái)實(shí)現(xiàn).
where語(yǔ)句 使你能夠要求一個(gè)關(guān)聯(lián)類型必須遵循一個(gè)特定的協(xié)議,或類型參數(shù)和關(guān)聯(lián)類型必須是相同的。
你可以這樣寫where語(yǔ)句:
在類型參數(shù)列表后面放置 where ,然后放置 針對(duì)關(guān)聯(lián)類型的約束 或 類型參數(shù)和關(guān)聯(lián)類型間的等價(jià)關(guān)系。
下面的例子定義了一個(gè)名為allItemsMatch的泛型函數(shù),用來(lái)檢查兩個(gè)Container實(shí)例是否包含相同順序的相同元素。如果所有的元素能夠匹配,那么返回一個(gè)為true的Boolean值,反之則為false.
被檢查的兩個(gè)Container可以不是相同類型的容器(雖然它們可以是),但它們確實(shí)擁有相同類型的元素。這個(gè)需求通過(guò)一個(gè)類型約束和where語(yǔ)句結(jié)合來(lái)表示:
<pre><code>`
func allItemsMatch
<C1: Container, C2: Container where C1.ItemType == C2.ItemType, C1.ItemType: Equatable>//where語(yǔ)句作為類型參數(shù)列表的一部分
(someContainer: C1, anotherContainer: C2)//方法的參數(shù)
-> Bool//方法的返回值
{
// 檢查兩個(gè)Container的元素個(gè)數(shù)是否相同
if someContainer.count != anotherContainer.count
{
return false
}
// 檢查兩個(gè)Container相應(yīng)位置的元素彼此是否相等
for i in 0..<someContainer.count
{
if someContainer[i] != anotherContainer[i]
{
return false
}
}
// 如果所有元素檢查都相同則返回true
return true
}
`</code></pre>
這個(gè)函數(shù)用了兩個(gè)參數(shù)someContainer和anotherContainer
someContainer參數(shù)是類型C1,anotherContainer參數(shù)是類型C2
C1和C2是兩個(gè)占位類型代表的具體的Container類型在函數(shù)被調(diào)用是才會(huì)被決定.
該函數(shù)的類型參數(shù)的要求:
1.C1必須遵循Container協(xié)議(寫作C1: Container)。
2.C2必須遵循Container協(xié)議(寫作C2: Container)。
3.C1的ItemType同樣是C2的ItemType(寫作C1.ItemType == C2.ItemType)。
4.C1的ItemType必須遵循Equatable協(xié)議(寫作C1.ItemType: Equatable)。
第3個(gè)和第4個(gè)要求被定義為where語(yǔ)句的一部分,寫在關(guān)鍵字where后面,作為函數(shù)類型參數(shù)列表的一部分。
這些要求意思是:
someContainer是一個(gè)C1類型的容器。
anotherContainer是一個(gè)C2類型的容器。
someContainer和anotherContainer包含相同的元素類型。
someContainer中的元素可以通過(guò)不等于操作( != )來(lái)檢查它們是否彼此不同。
第3個(gè)和第4個(gè)要求結(jié)合起來(lái)的意思是anotherContainer中的元素也可以通過(guò)!=操作來(lái)檢查,因?yàn)樗鼈冊(cè)趕omeContainer中元素確實(shí)是相同的類型。
這些要求能夠使allItemsMatch函數(shù)比較兩個(gè)容器,即便它們是不同的Container類型。
allItemsMatch首先檢查兩個(gè)容器是否擁有同樣數(shù)目的items,如果它們的元素?cái)?shù)目不同,便沒(méi)有辦法進(jìn)行匹配,函數(shù)就會(huì)返回false 。
檢查完之后,函數(shù)通過(guò)for-in循環(huán)和半閉區(qū)間操作( ..< )來(lái)迭代 someContainer中的所有元素。對(duì)于每個(gè)元素,函數(shù)檢查是否 someContainer中的元素不等于對(duì)應(yīng)的anotherContainer中的元素,如果這兩個(gè)元素不等,則這兩個(gè)容器不匹配,返回false 。
如果循環(huán)體結(jié)束后未發(fā)現(xiàn)沒(méi)有任何的不匹配,那表明兩個(gè)容器匹配,函數(shù)返回 true 。
allItemsMatch函數(shù)示例:
<pre><code>var stackOfStrings = Stack<String>() stackOfStrings.push("uno") stackOfStrings.push("dos") stackOfStrings.push("tres") let arrayOfStrings = ["uno", "dos", "tres"] if allItemsMatch(stackOfStrings, anotherContainer: arrayOfStrings) { print("All items match.") } else { print("Not all items match.") } // All items match.</code></pre>
上面的例子創(chuàng)建一個(gè)Stack實(shí)例來(lái)存儲(chǔ)字符串,然后壓入三個(gè)字符串進(jìn)棧。這個(gè)例子也創(chuàng)建了一個(gè)Array實(shí)例,并初始化包含三個(gè)同棧里一樣的字符串。即便棧和數(shù)組是不同的類型,但它們都遵循Container協(xié)議,而且它們都包含同樣的類型值。因此你可以調(diào)用allItemsMatch函數(shù),用這兩個(gè)容器作為它的參數(shù)。在上面的例子中, allItemsMatch函數(shù)正確的顯示了這兩個(gè)容器的所有元素都是相互匹配的。