泛型(Generics)
- 泛型可以將類型參數化,提高代碼複用率,減少代碼量
func swapValues<T>(_ a: inout T, _ b: inout T) {
(a, b) = (b, a)
}
var i1 = 10
var i2 = 20
swapValues(&i1, &i2)
print(i1, i2) // 20, 10 i1和i2的值交換了
var d1 = 10.0
var d2 = 20.0
swapValues(&d1, &d2)
print(d1, d2) //20.0, 10.0 d1和d2的值交換了
struct Date {
var year = 0, month = 0, day = 0
}
var dd1 = Date(year: 2011, month: 9, day: 10)
var dd2 = Date(year: 2012, month: 10, day: 11)
swapValues(&dd1, &dd2)
print(dd1,dd2)//Date(year: 2012, month: 10, day: 11), Date(year: 2011, month: 9, day: 10) dd1和dd2的值交換了
通過反彙編看一下上面的泛型交換值函數的額原理:
T爲Int:
T爲Double:
可以看出,雖然泛型的類型不同,但是可以看出兩個函數的地址都是0x100001800,調用的同一個函數,可以區分不同類型進行計算,只要是靠metadata也就是元類型來實現的
- 泛型函數賦值給變量,也是可以說賦值給參數
- 泛型的舉例應用:
- 類的泛型
類泛型的繼承:
記住要寫泛型<E>
2. 結構體的泛型
因爲push會添加元素到數組,pop會刪除元素從數組,都會改變內存,所以要加mutating
Score<Int>.grade("A")需要加Int是因爲初始化時就要告訴泛型的類型,即使你沒調用泛型
關聯類型
- 關聯類型的作用:給協議中用到的類型定義一個佔位名稱
- 協議中可以擁有多個關聯類型
使用typealias給關聯類型設置真實類型可以省略,因爲會根據你傳入的參數類型自動識別
也可以傳入一個泛型來代替關聯類型
類型約束
針對泛型的類型約束:
protocol Runnable {
}
class Person {
}
//要求傳入的泛型既是Person或Person 的子類,又要遵守Runnable協議
func swapValues<T: Person & Runnable>(_ a: inout T, _ b: inout T) {
(a, b) = (b, a)
}
針對關聯類型的約束:關聯類型遵守Equatable協議,泛型也要遵守相同的協議:
protocol Stackable {
associatedtype Element: Equatable
}
class Stack<E: Equatable> : Stackable {typealias Element = E}
多個泛型結合where的複雜約束:
S1和S2兩個泛型都遵守Stackable協議,並且S1和S2的Element屬於同一類型,S1的Element遵守Hashable協議
func equal<S1: Stackable, S2: Stackable>(_ s1: S1, _ s2: S2) -> Bool where S1.Element == S2.Element, S1.Element : Hashable{
return true
}
var stack1 = Stack<Int>()
var stack2 = Stack<Int>()
var isEqual = equal(stack1, stack2)
print(isEqual) //true
但是當stack1和stack2的泛型類型不一致會報錯:
協議類型的注意點
protocol Runnable { }
class Person: Runnable { }
class Car: Runnable { }
func get(_ type: Int) -> Runnable {
if type == 0 {
return Person()
}
return Car()
}
var r1 = get(0)
var r2 = get(1)
print(r1, r2) //TestSwift.Person TestSwift.Car
由上面的方法可知,get方法需要返回一個遵守Runnable 協議的值,而Person和Car正好都遵守Runnable 協議,所以正確
- 如果協議中有associatedtype
具體報的錯誤:
報錯的原因是這種寫法,編譯器在編譯時期不能確定associatedtype(關聯類型)的值
順便說一句,作爲參數會有同樣的錯誤:
協議類型錯誤的解決方案
1. 使用泛型解決
func get<T : Runnable>(_ type: Int) -> T {
if type == 0 {
return Person() as! T
}
return Car() as! T
}
var r1: Person = get(0)
var r2: Car = get(1)
成功原因:這種情況下,我們就明確了get(0)的返回值是Person類型,get(1)的返回值是Car類型,這樣在編譯時期就可以知道相對應的associatedtype(關聯類型)的值,所以正確
但是這種寫法存在風險,如果你把代碼寫成var r1:Car = get(0),這樣當走入get方法時代碼就會變爲Person as!Car,這樣把Person類型強制轉換爲Car類型,就會報錯的
2. 使用some關鍵字聲明一個不透明類型,不透明類型就是some修飾的返回值只知道是遵守Runnable協議的,具體是什麼類型不知道,實際類型的信息更不知道,並且some會強制只有一個返回值類型
這種寫法限制了只返回一種類型,這樣編譯器就會知道返回值類型,從而知道associatedtype(關聯類型)的值,所以正確
some
- some限制只能返回一種類型
- some除了用在返回值類型上,一般還可以用在屬性類型上
這時的屬性只知道遵守Runnable協議,不知道它的具體類型,更不知道具體類型的屬性和方法。