泛型代碼可以讓你寫出根據(jù)自我需求定義、適用于任何類型的锡宋,靈活且可重用的函數(shù)和類型儡湾。它可以讓你避免重復(fù)的代碼,用一種清晰和抽象的方式來表達代碼的意圖执俩。
泛型是 Swift 強大特征中的其中一個徐钠,許多 Swift 標準庫是通過泛型代碼構(gòu)建出來的。事實上役首,泛型的使用貫穿了整本語言手冊敷鸦,只是你沒有發(fā)現(xiàn)而已濒旦。例如,Swift 的數(shù)組和字典類型都是泛型集。你可以創(chuàng)建一個Int數(shù)組辆亏,也可創(chuàng)建一個String數(shù)組,或者甚至于可以是任何其他 Swift 的類型數(shù)據(jù)數(shù)組又活。同樣的兽泄,你也可以創(chuàng)建存儲任何指定類型的字典(dictionary),而且這些類型可以是沒有限制的乏屯。
定義一個交換量販int值得方法根时, 注意inout關(guān)鍵字,代表函數(shù)內(nèi)部可以修改函數(shù)外部的參數(shù)值
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
let temporaryA = a
a = b
b = temporaryA
}
var someInt = 3
var anotherInt = 107
swapTwoInts(&someInt, &anotherInt)
print("someInt is now \(someInt), and anotherInt is now \(anotherInt)")
// Prints "someInt is now 107, and anotherInt is now 3"
該swapTwoInts(::)功能是有用的辰晕,但它只能與所用Int的值蛤迎。如果你想交換兩個String值,或兩個Double值含友,你必須寫更多的功能替裆,如swapTwoStrings(::)與swapTwoDoubles(::)如下功能:
func swapTwoStrings(_ a: inout String, _ b: inout String) {
let temporaryA = a
a = b
b = temporaryA
}
func swapTwoDoubles(_ a: inout Double, _ b: inout Double) {
let temporaryA = a
a = b
b = temporaryA
}
您可能已經(jīng)注意到swapTwoInts(::)校辩,swapTwoStrings(::)和swapTwoDoubles(::)功能是相同的。唯一的區(qū)別是該值的辆童,他們接受的類型(Int宜咒,String,和Double)把鉴。
但實際應(yīng)用中通常需要一個用處更強大并且盡可能的考慮到更多的靈活性單個函數(shù)故黑,可以用來交換兩個任何類型值,很幸運的是庭砍,泛型代碼幫你解決了這種問題场晶。(一個這種泛型函數(shù)后面已經(jīng)定義好了。)
注意: 在所有三個函數(shù)中怠缸,a和b的類型是一樣的诗轻。如果a和b不是相同的類型,那它們倆就不能互換值揭北。Swift 是類型安全的語言扳炬,所以它不允許一個String類型的變量和一個Double類型的變量互相交換值。如果一定要做搔体,Swift 將報編譯錯誤鞠柄。
泛型函數(shù)
泛型函數(shù)可以工作于任何類型,這里是一個上面swapTwoInts函數(shù)的泛型版本嫉柴,用于交換兩個值:
func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
let temporaryA = a
a = b
b = temporaryA
}
swapTwoValues函數(shù)主體和swapTwoInts函數(shù)是一樣的厌杜,它只在第一行稍微有那么一點點不同于swapTwoInts,如下所示:
func swapTwoInts(_ a: inout Int, _ b: inout Int)
func swapTwoValues<T>(_ a: inout T, _ b: inout T)
這個函數(shù)的泛型版本使用了占位類型名字(通常此情況下用字母T來表示)來代替實際類型名(如In计螺、String或Doubl)夯尽。占位類型名沒有提示T必須是什么類型,但是它提示了a和b必須是同一類型T登馒,而不管T表示什么類型匙握。只有swapTwoValues函數(shù)在每次調(diào)用時所傳入的實際類型才能決定T所代表的類型。
另外一個不同之處在于這個泛型函數(shù)名后面跟著的展位類型名字(T)是用尖括號括起來的()陈轿。這個尖括號告訴 Swift 那個T是swapTwoValues函數(shù)所定義的一個類型圈纺。因為T是一個占位命名類型,Swift 不會去查找命名為T的實際類型麦射。
swapTwoValues函數(shù)除了要求傳入的兩個任何類型值是同一類型外蛾娶,也可以作為swapTwoInts函數(shù)被調(diào)用。每次swapTwoValues被調(diào)用潜秋,T所代表的類型值都會傳給函數(shù)蛔琅。
var someInt = 3
var anotherInt = 107
swapTwoValues(&someInt, &anotherInt)
// someInt is now 107, and anotherInt is now 3
var someString = "hello"
var anotherString = "world"
swapTwoValues(&someString, &anotherString)
// someString is now "world", and anotherString is now "hello"
泛型類型
通常在泛型函數(shù)中,Swift 允許你定義你自己的泛型類型峻呛。這些自定義類罗售、結(jié)構(gòu)體和枚舉作用于任何類型辜窑,如同Array和Dictionary的用法。
這里展示了如何寫一個非泛型版本的棧寨躁,Int值型的棧:
struct IntStack {
var items = [Int]()
//入棧
mutating func push(_ item: Int) {
items.append(item)
}
mutating func pop() -> Int {
//出棧
return items.removeLast()
}
}
上面所展現(xiàn)的IntStack類型只能用于Int值穆碎,不過,其對于定義一個泛型Stack類(可以處理任何類型值的棧)是非常有用的职恳。
struct Stack<Element> {
var items = [Element]()
mutating func push(_ item: Element) {
items.append(item)
}
mutating func pop() -> Element {
return items.removeLast()
}
}
注意到Stack的泛型版本基本上和非泛型版本相同所禀,但是泛型版本的占位類型參數(shù)為Element代替了實際Int類型。這種類型參數(shù)包含在一對尖括號里(< Element >)话肖,緊隨在結(jié)構(gòu)體名字后面北秽。
T定義了一個名為“某種類型Element”的節(jié)點提供給后來用葡幸。這種將來類型可以在結(jié)構(gòu)體的定義里任何地方表示為“ Element”最筒。在這種情況下,Element在如下三個地方被用作節(jié)點:
- 創(chuàng)建一個名為items的屬性床蜘,使用空的Element類型值數(shù)組對其進行初始化;
- 指定一個包含一個參數(shù)名為item的push方法蔑水,該參數(shù)必須是Element類型丹擎;
- 指定一個pop方法的返回值榜苫,該返回值將是一個Element類型值护戳。
當創(chuàng)建一個新單例并初始化時, 通過用一對緊隨在類型名后的尖括號里寫出實際指定棧用到類型垂睬,創(chuàng)建一個Stack實例媳荒,同創(chuàng)建Array和Dictionary一樣:
您創(chuàng)建一個新的Stack寫入存儲在尖括號內(nèi)堆棧中的類型實例。例如驹饺,創(chuàng)建一個新的字符串堆棧钳枕,你寫的Stack<String>():
var stackOfStrings = Stack<String>()
stackOfStrings.push("uno")
stackOfStrings.push("dos")
stackOfStrings.push("tres")
stackOfStrings.push("cuatro")
// the stack now contains 4 strings
從彈出堆棧中刪除一個值,并返回最高值赏壹,"cuatro":
let fromTheTop = stackOfStrings.pop()
// fromTheTop is equal to "cuatro", and the stack now contains 3 strings
這里的堆椕床看起來如何彈出它的最高值后:
擴展泛型類型
下面的例子擴展了通用的Stack類型添加一個只讀屬性來計算所謂的topItem,它返回堆棧頂部的項目卡儒,而不從棧中彈出它:
extension Stack {
var topItem: Element? {
return items.isEmpty ? nil : items[items.count - 1]
}
}
該topItem屬性返回類型的可選值Element田柔。如果堆棧是空的俐巴,topItem返回nil; 如果棧不為空,topItem將返回在最后一個項目items數(shù)組硬爆。
請注意欣舵,這個擴展沒有定義類型參數(shù)列表。相反缀磕,Stack類型的現(xiàn)有類型的參數(shù)名缘圈,Element時,在擴展中用于指示可選類型的topItem計算屬性袜蚕。
在topItem計算財產(chǎn)現(xiàn)在可以與任何使用Stack實例來訪問糟把,而沒有刪除它查詢其頂部的項目:
if let topItem = stackOfStrings.topItem {
print("The top item on the stack is \(topItem).")
}
// Prints "The top item on the stack is tres."
類型約束
swapTwoValues函數(shù)和Stack類型可以作用于任何類型,不過牲剃,有的時候?qū)κ褂迷诜盒秃瘮?shù)和泛型類型上的類型強制約束為某種特定類型是非常有用的遣疯。類型約束指定了一個必須繼承自指定類的類型參數(shù),或者遵循一個特定的協(xié)議或協(xié)議構(gòu)成凿傅。
類型約束語法
你可以寫一個在一個類型參數(shù)名后面的類型約束缠犀,通過冒號分割,來作為類型參數(shù)鏈的一部分聪舒。這種作用于泛型函數(shù)的類型約束的基礎(chǔ)語法如下所示(和泛型類型的語法相同):
func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
// function body goes here
}
上面這個假定函數(shù)有兩個類型參數(shù)辨液。第一個類型參數(shù)T,有一個需要T必須是SomeClass子類的類型約束箱残;第二個類型參數(shù)U滔迈,有一個需要U必須遵循SomeProtocol協(xié)議的類型約束。
類型約束行為
這里有個名為findStringIndex的非泛型函數(shù)被辑,該函數(shù)功能是去查找包含一給定String值的數(shù)組燎悍。若查找到匹配的字符串,findStringIndex函數(shù)返回該字符串在數(shù)組中的索引值(Int)敷待,反之則返回nil:
func findIndex(ofString valueToFind: String, in array: [String]) -> Int? {
for (index, value) in array.enumerated() {
if value == valueToFind {
return index
}
}
return nil
}
findStringIndex函數(shù)可以作用于查找一字符串數(shù)組中的某個字符串:
let strings = ["cat", "dog", "llama", "parakeet", "terrapin"]
if let foundIndex = findIndex(ofString: "llama", in: strings) {
print("The index of llama is \(foundIndex)")
}
// Prints "The index of llama is 2"
如果只是針對字符串而言查找在數(shù)組中的某個值的索引间涵,用處不是很大,不過榜揖,你可以寫出相同功能的泛型函數(shù)findIndex勾哩,用某個類型T值替換掉提到的字符串。
func findIndex<T>(of valueToFind: T, in array:[T]) -> Int? {
for (index, value) in array.enumerated() {
if value == valueToFind {
return index
}
}
return nil
}
上面所寫的函數(shù)不會編譯举哟。這個問題的位置在等式的檢查上思劳,“if value == valueToFind”。不是所有的 Swift 中的類型都可以用等式符(==)進行比較妨猩。例如潜叛,如果你創(chuàng)建一個你自己的類或結(jié)構(gòu)體來表示一個復(fù)雜的數(shù)據(jù)模型,那么 Swift 沒法猜到對于這個類或結(jié)構(gòu)體而言“等于”的意思。正因如此威兜,這部分代碼不能可能保證工作于每個可能的類型T销斟,當你試圖編譯這部分代碼時估計會出現(xiàn)相應(yīng)的錯誤。
不過椒舵,所有的這些并不會讓我們無從下手蚂踊。Swift 標準庫中定義了一個Equatable協(xié)議,該協(xié)議要求任何遵循的類型實現(xiàn)等式符(==)和不等符(!=)對任何兩個該類型進行比較笔宿。所有的 Swift 標準類型自動支持Equatable協(xié)議犁钟。
任何Equatable類型都可以安全的使用在findIndex函數(shù)中,因為其保證支持等式操作泼橘。為了說明這個事實涝动,當你定義一個函數(shù)時,你可以寫一個Equatable類型約束作為類型參數(shù)定義的一部分:
func findIndex<T: Equatable>(of valueToFind: T, in array:[T]) -> Int? {
for (index, value) in array.enumerated() {
if value == valueToFind {
return index
}
}
return nil
}
findIndex中這個單個類型參數(shù)寫做:T: Equatable炬灭,也就意味著“任何T類型都遵循Equatable協(xié)議”醋粟。
findIndex函數(shù)現(xiàn)在則可以成功的編譯過,并且作用于任何遵循Equatable的類型担败,如Double或String:
let doubleIndex = findIndex(of: 9.3, in: [3.14159, 0.1, 0.25])
// doubleIndex is an optional Int with no value, because 9.3 is not in the array
let stringIndex = findIndex(of: "Andrea", in: ["Mike", "Malcolm", "Andrea"])
// stringIndex is an optional Int containing a value of 2