泛型代碼讓你能夠根據(jù)自定義的需求,編寫出適用于任意類型、靈活可重用的函數(shù)及類型。它能讓你避免代碼的重復(fù),用一種清晰和抽象的方式來表達(dá)代碼的意圖。
泛型是Swift最強(qiáng)大的特性之一,許多 Swift 標(biāo)準(zhǔn)庫是通過泛型代碼構(gòu)建的。事實(shí)上,泛型的使用貫穿了整本語言手冊,只是你可能沒有發(fā)現(xiàn)而已。例如,Swift 的Array 和Dictionary 都是泛型集合。你可以創(chuàng)建一個 Int數(shù)組,也可創(chuàng)建一個String數(shù)組,甚至可以是任意其他 Swift 類型的數(shù)組。同樣的,你也可以創(chuàng)建存儲任意指定類型的字典。
1.泛型所解決的問題
下面是一個標(biāo)準(zhǔn)的非泛型函數(shù) swapTwoInts(::),用來交換兩個 Int 值:
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
let temporaryA = a
a = b
b = temporaryA
}
這個函數(shù)使用輸入輸出參數(shù)(inout)來交換 a 和 b 的值,請參考輸入輸出參數(shù)。
swapTwoInts(_:_:)函數(shù)交換 b 的原始值到 a,并交換 a 的原始值到 b。你可以調(diào)用這個函數(shù)交換兩個 Int 變量的值:
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”
誠然,swapTwoInts(::) 函數(shù)挺有用,但是它只能交換 Int 值,如果你想要交換兩個 String 值或者Double`值,就不得不寫更多的函數(shù)。
2.泛型函數(shù)
泛型函數(shù)可以適用于任何類型,下面的swapTwoValues(_:_:) 函數(shù)是上面三個函數(shù)的泛型版本:
func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
let temporaryA = a
a = b
b = temporaryA
}
swapTwoValues(_:_:)的函數(shù)主體和 swapTwoInts(_:_:)函數(shù)是一樣的,它們只在第一行有點(diǎn)不同,如下所示:
func swapTwoInts(_ a: inout Int, _ b: inout Int)
func swapTwoValues<T>(_ a: inout T, _ b: inout T)”
占位類型名沒有指明 T 必須是什么類型,但是它指明了 a 和 b 必須是同一類型 T,無論 T 代表什么類型。
3.泛型類型
除了泛型函數(shù),Swift 還允許你定義泛型類型。這些自定義類、結(jié)構(gòu)體和枚舉可以適用于任何類型,類似于 Array 和Dictionary。
這部分內(nèi)容將向你展示如何編寫一個名為 Stack (棧)的泛型集合類型。棧是一系列值的有序集合,和 Array類似,但它相比 Swift的 Array 類型有更多的操作限制。數(shù)組允許在數(shù)組的任意位置插入新元素或是刪除其中任意位置的元素。而棧只允許在集合的末端添加新的元素(稱之為入棧)。類似的,棧也只能從末端移除元素(稱之為出棧)。
struct Stack<Element> {
var items = [Element]()
mutating func push(_ item: Element) {
items.append(item)
}
mutating func pop() -> Element {
return items.removeLast()
}
}
Element為待提供的類型定義了一個占位名。這種待提供的類型可以在結(jié)構(gòu)體的定義中通過 Element 來引用。在這個例子中,Element在如下三個地方被用作占位符:
創(chuàng)建 items 屬性,使用Element 類型的空數(shù)組對其進(jìn)行初始化。
指定 push(_:)方法的唯一參數(shù) item的類型必須是 Element類型。
指定 pop()方法的返回值類型必須是Element類型。
4.擴(kuò)展一個泛型類型
當(dāng)你擴(kuò)展一個泛型類型的時候,你并不需要在擴(kuò)展的定義中提供類型參數(shù)列表。原始類型定義中聲明的類型參數(shù)列表在擴(kuò)展中可以直接使用,并且這些來自原始類型中的參數(shù)名稱會被用作原始定義中類型參數(shù)的引用。
下面的例子擴(kuò)展了泛型類型 Stack,為其添加了一個名為 topItem 的只讀計(jì)算型屬性,它將會返回當(dāng)前棧頂端的元素而不會將其從棧中移除:
extension Stack {
var topItem: Element? {
return items.isEmpty ? nil : items[items.count - 1]
}
}
topItem 屬性會返回一個 Element 類型的可選值。當(dāng)棧為空的時候,topItem 會返回 nil;當(dāng)棧不為空的時候,topItem 會返回 items 數(shù)組中的最后一個元素。
注意,這個擴(kuò)展并沒有定義一個類型參數(shù)列表。相反的,Stack 類型已有的類型參數(shù)名稱 Element,被用在擴(kuò)展中來表示計(jì)算型屬性 topItem 的可選類型。
計(jì)算型屬性 topItem 現(xiàn)在可以用來訪問任意 Stack 實(shí)例的頂端元素且不移除它:
if let topItem = stackOfStrings.topItem {
print("The top item on the stack is \(topItem).")
}
// 打印 “The top item on the stack is tres.”
5.類型約束語法
你可以在一個類型參數(shù)名后面放置一個類名或者協(xié)議名,并用冒號進(jìn)行分隔,來定義類型約束,它們將成為類型參數(shù)列表的一部分。對泛型函數(shù)添加類型約束的基本語法如下所示(作用于泛型類型時的語法與之相同):
func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
// 這里是泛型函數(shù)的函數(shù)體部分
}
上面這個函數(shù)有兩個類型參數(shù)。第一個類型參數(shù) T,有一個要求 T 必須是 SomeClass 子類的類型約束;第二個類型參數(shù) U,有一個要求 U 必須符合 SomeProtocol 協(xié)議的類型約束。
類型約束實(shí)踐
這里有個名為findIndex(ofString:in:)的非泛型函數(shù),該函數(shù)的功能是在一個String 數(shù)組中查找給定 String值的索引。若查找到匹配的字符串,findIndex(ofString:in:) 函數(shù)返回該字符串在數(shù)組中的索引值,否則返回 nil:
func findIndex(ofString valueToFind: String, in array: [String]) -> Int? {
for (index, value) in array.enumerated() {
if value == valueToFind {
return index
}
}
return nil
}
findIndex(ofString:in:) 函數(shù)可以用于查找字符串?dāng)?shù)組中的某個字符串:
let strings = ["cat", "dog", "llama", "parakeet", "terrapin"]
if let foundIndex = findIndex(ofString: "llama", in: strings) {
print("The index of llama is \(foundIndex)")
}
如果上面的代碼改成泛型版本的寫法,
func findIndex<T>(of valueToFind: T, in array:[T]) -> Int? {
for (index, value) in array.enumerated() {
if value == valueToFind {
return index
}
}
return nil
}
這種寫法將編譯不通過,問題出在相等性檢查上,即"if value == valueToFind"。不是所有的 Swift 類型都可以用等式符(==)進(jìn)行比較。
Swift 標(biāo)準(zhǔn)庫中定義了一個 Equatable 協(xié)議,該協(xié)議要求任何遵循該協(xié)議的類型必須實(shí)現(xiàn)等式符(==)及不等符(!=),從而能對該類型的任意兩個值進(jìn)行比較。所有的 Swift 標(biāo)準(zhǔn)類型自動支持 Equatable 協(xié)議。
所以講上面的代碼改成下面的就可以了:
func findIndex<T: Equatable>(of valueToFind: T, in array:[T]) -> Int? {
for (index, value) in array.enumerated() {
if value == valueToFind {
return index
}
}
return nil
}
5.泛型where語句
類型約束讓你能夠?yàn)榉盒秃瘮?shù)或泛型類型的類型參數(shù)定義一些強(qiáng)制要求。
為關(guān)聯(lián)類型定義約束也是非常有用的。你可以在參數(shù)列表中通過 where 子句為關(guān)聯(lián)類型定義約束。你能通過 where子句要求一個關(guān)聯(lián)類型遵從某個特定的協(xié)議,以及某個特定的類型參數(shù)和關(guān)聯(lián)類型必須類型相同。你可以通過將 where 關(guān)鍵字緊跟在類型參數(shù)列表后面來定義where子句,where 子句后跟一個或者多個針對關(guān)聯(lián)類型的約束,以及一個或多個類型參數(shù)和關(guān)聯(lián)類型間的相等關(guān)系。你可以在函數(shù)體或者類型的大括號之前添加 where子句。
func allItemsMatch<C1: Container, C2: Container>(_ someContainer: C1, _ anotherContainer: C2)
-> Bool where C1.ItemType == C2.ItemType, C1.ItemType: Equatable {
// 檢查兩個容器含有相同數(shù)量的元素
if someContainer.count != anotherContainer.count {
return false
}
// 檢查每一對元素是否相等
for i in 0..<someContainer.count {
if someContainer[i] != anotherContainer[i] {
return false
}
}
// 所有元素都匹配,返回 true
return true
}
演示使用:
var stackOfStrings = Stack<String>()
stackOfStrings.push("uno")
stackOfStrings.push("dos")
stackOfStrings.push("tres")
var arrayOfStrings = ["uno", "dos", "tres"]
if allItemsMatch(stackOfStrings, arrayOfStrings) {
print("All items match.")
} else {
print("Not all items match.")
}
// 打印 “All items match.