本文基於Swift5.0版本官方文檔,閱讀大概須要20min,能夠對泛型有一個清晰的認識。
html
泛型:指的就是在你定義的時候放置一個佔位符類型名,告訴系統使用的類型如今不肯定,我先佔個位置。這樣編譯的時候系統不會報錯。而後在你使用的時候纔會真正地肯定類型。swift
語言表達起來可能不是那麼直觀,讓咱們看一段代碼直觀的瞭解一下什麼是泛型。數組
//定義一個打印任意類型變量的函數
func printSomething<T>(value1: T, value2: T) {
print(value1, value2)
}
printSomething(value1: 1, value2: 2) // Int: 1 2
printSomething(value1: "a", value2: "b") //String: a b
複製代碼
經過上面的代碼咱們能夠看出,經過在函數名字後面添加<T>
來代表添加了一個泛型類型,<>
告訴編譯器T
是一個佔位符類型,不須要真正的查找叫作T
的類型。安全
<T>
中的T能夠是任意字符或者單詞,可是要使用大寫字母或者大寫開頭的駝峯命名法(如:V
、U
、MyTypeParameter
)。<>
裏面不止能夠寫一個類型佔位符,也能夠寫多個:<T, U>
。如今咱們初步瞭解泛型是什麼,那麼確定會有人問道:咱們爲何要是使用泛型呢?下面咱們看一下爲何要使用泛型。bash
在平常工做中,咱們會常常遇到在某些條件下交換兩個變量的值的狀況。若是須要交換兩個Int
的值得話,咱們能夠很輕易的實現下面的函數:數據結構
func swapTwoIntValue(_ num1: inout Int, _ num2: inout Int) {
(num1, num2) = (num2, num1)
}
var num1 = 10
var num2 = 20
swapTwoIntValue(&num1, &num2)
print(num1, num2) // 20 10
複製代碼
這個函數很簡潔,也很正確,可是若是咱們還須要交換String
、Double
等等類型的變量呢,再寫swapTwoStringValue(_:_:)
、swapTwoDoubleValue(_:_:)
的函數嗎?再定義兩個這樣的函數固然沒有問題,可是咱們會發現這三個函數內部實現都是同樣的,區別只是參數的類型不一樣。這時候就輪到泛型出馬了,咱們能夠用泛型寫一個使用任意Swift基本類型的交換函數:app
func swapTwoValue<T>(_ num1: inout T, _ num2: inout T) {
(num1, num2) = (num2, num1)
}
var num1 = 10
var num2 = 20
swapTwoValue(&num1, &num2)
print(num1, num2) // 20 10
var str1 = "hello"
var str2 = "world"
swapTwoValue(&str1, &str2)
print(str1, str2) // world hello
複製代碼
swapTwoValue(_:_:)
函數只是舉個例子說明泛型類型函數的用法,若是你想使用交換兩個變量的值得功能,你可使用官方的swap(_:_:)
函數。T
,雖然T
能夠表示任意類型,但兩個變量必須是同一類型,Swift不容許兩個不一樣類型的變量交換值,由於Swift是一門類型安全的語言。咱們如今知道能夠經過定義泛型類型的函數來達到減小代碼冗餘的問題,那麼泛型的用處僅僅如此嗎?做爲Swift最強大的特性之一,確定不會只是實現一個泛型類型函數這麼簡單的。下面讓咱們看一下泛型還能作什麼?ide
咱們能夠經過泛型來實現一個支持多種類型的棧。具體代碼以下:函數
struct Stack<Element> {
var items = [Element]()
mutating func push(_ item: Element) {
items.append(item)
}
mutating func pop(_ item: Element) -> Element {
return items.removeLast()
}
}
複製代碼
Stack能夠放入Int
/String
等多種類型的數據。此處有個地方要注意:在咱們給Stack擴展計算屬性或者方法的時候,不須要咱們在聲明類型參數,Stack中的泛型在extension中依然有效
。具體代碼以下:ui
// 不須要再用<Element>來聲明泛型
extension Stack {
var topItem: Element? {
return items.isEmpty ? nil : items[items.count - 1]
}
}
複製代碼
平常開發中,咱們會常常須要實如今一個數組中查找某個值的索引的功能,若是咱們將數組的元素類型寫死的話,咱們聲明的函數只能用於某一種類型,這時候咱們應該怎麼辦呢?對了,就是將類型聲明爲泛型類型。經過上面的介紹,咱們可能會寫下如下代碼:
func findIndex<T>(_ target: T, _ array: [T]) -> Int? {
for (index, value) in array.enumerated() {
if value == target {
return index
}
}
return nil
}
複製代碼
這個函數建立的很不錯,可是很惋惜它編譯會報錯:Binary operator '==' cannot be applied to two 'T' operands
,該報錯表示咱們聲明的類型佔位符T
不能使用==
運算符。那麼如何正確的實現該函數呢?這就要使用類型約束(Type Constraints)來實現了。具體的作法就是將findIndex<T>
改成<T: Equatable>
,這句話的意思是T
只支持實現了Equatable
協議的類型使用。具體代碼以下:
func findIndex<T: Equatable>(_ target: T, _ array: [T]) -> Int? {
for (index, value) in array.enumerated() {
if value == target {
return index
}
}
return nil
}
if let index = findIndex("person0", arr) {
print("person0 index is \(index)") //person0 index is 0
}
複製代碼
如今咱們知道能夠在函數參數中使用泛型,那麼我能在protocol
中實現相似的功能嗎?答案是:固然能夠。咱們能夠用associatedtype
關鍵字來告訴編譯器該類型爲泛型,在真正使用的時候再檢查它的類型。
假如咱們要實現一個Container
的Protocol,該協議包含了append(_)
添加元素的函數、獲取長度的計算屬性count
、根據下標獲取元素的函數subscript(_)
。這時候若是咱們將Item的類型寫死的話就說明了只有這一種類型可以遵照該Protocol,那麼如何讓更多的類型可以遵照呢?這時候就輪到associatedtype
出場了。下面爲具體代碼:
protocol Container {
associatedtype Item
mutating func append(_ item: Item)
var count: Int { get }
subscript(i: Int) -> Item { get }
}
複製代碼
咱們可使Stack
遵照該協議,看一下具體使用。代碼以下:
extension Stack: Container {
mutating func append(_ item: Element) {
push(item)
}
var count: Int {return items.count }
subscript (_ i: Int) -> Element {
return items[i]
}
}
var s1 = Stack(items: [1,2,3,4])
var s2 = Stack(items: ["a", "b", "c", "d"])
s1.append(5)
print(s1.items) //[1,2,3,4,5]
print(s1.count) //5
print(s1[2]) // 3
s2.append("f")
print(s2.items) //["a", "b", "c", "d", "f"]
print(s2.count) //5
print(s2[2]) //"f"
複製代碼
在上面咱們看到Protocol中可使用聯合類型來實現泛型,那麼咱們也能夠給聯合類型添加類型約束來實現泛型遵照某個Protocol、或者遵照某種條件(好比類型相同等)。具體代碼以下:
protocol Container {
//該行代碼表示Item必須是遵照Equatable的類型
associatedtype Item: Equatable
mutating func append(_ item: Item)
var count: Int { get }
subscript(_ i: Int) -> Item { get }
}
複製代碼
咱們知道能夠給聯合類型添加類型約束,能夠用associatedtype Item: Equatable
來使Item遵照Equatable協議,那若是我想讓Item遵照Equatable的同時,又約束它必須是某一種類型呢?這時候咱們可使用where
語句來實現。具體代碼以下:
protocol SuffixableContainer: Container {
//該行代碼表示Suffix必須遵照SuffixableContainer,而且它的Item類型必須和Container的Item類型一致
associatedtype Suffix: SuffixableContainer where Suffix.Item == Item
func suffix(_ size: Int) -> Suffix
}
//Stack至關於上面的Suffix,它遵照了SuffixableContainer協議
extension Stack: SuffixableContainer {
func suffix(_ size: Int) -> Stack {
var result = Stack()
for index in (count-size)..<count {
result.append(self[index])
}
return result
}
}
var stackOfInts = Stack<Int>()
stackOfInts.append(10)
stackOfInts.append(20)
stackOfInts.append(30)
let suffix = stackOfInts.suffix(2)
// suffix 包含 20 和 30
複製代碼
上面的代碼SuffixableContainer
實現了一個獲取某個位置到最後的一段數據。
同Protocol,咱們也能夠在Extension中經過where
來實現類型約束。 若是咱們不讓Element遵照Equatable協議的話,是會編譯錯誤的,由於在該函數中咱們使用了 == 操做符
。代碼以下:
extension Stack where Element: Equatable {
func isTop(_ item: Element) -> Bool {
guard let topItem = items.last else {
return false
}
return topItem == item
}
}
if stackOfStrings.isTop("tres") {
print("Top element is tres.")
} else {
print("Top element is something else.")
}
// Prints "Top element is tres."
複製代碼
固然,咱們也能夠在擴展Protocol的時候來使用類型約束。代碼以下:
extension Array: Container where Element: Equatable { }
// 擴展Container,而且Item是遵照Equatable協議的
extension Container where Item: Equatable {
func startsWith(_ item: Item) -> Bool {
return count >= 1 && self[0] == item
}
}
if [9, 9, 9].startsWith(42) {
print("Starts with 42.")
} else {
print("Starts with something else.")
}
//"Starts with something else."
複製代碼
除了強制泛型元素遵照某個協議外,咱們也能夠強制泛型元素爲特定的某個類型。代碼以下:
extension Array: Container where Element: Equatable { }
extension Container where Item == Double {
func average() -> Double {
var sum = 0.0
for index in 0..<count {
sum += self[index]
}
return sum / Double(count)
}
}
print([1260.0, 1200.0, 98.6, 37.0].average()) //648.9
複製代碼
上面就是關於泛型的講解,下面來看一下關於泛型的總結。
類型參數
或有類型約束的參數
能夠在泛型函數、泛型下標、泛型類型中使用。where
語句可使你的聯合類型必須遵照某個協議或者知足某些條件。到這裏,關於泛型的講解就結束了。但願你們經過本文能對泛型有一個全新的、深入的認識。讓咱們在項目中愉快的使用泛型吧!