Swift學習:泛型,關聯類型,類型約束,協議類型及錯誤解決,some

泛型(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也就是元類型來實現的

  • 泛型函數賦值給變量,也是可以說賦值給參數

  • 泛型的舉例應用:
  1. 類的泛型

類泛型的繼承:

 記住要寫泛型<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協議,不知道它的具體類型,更不知道具體類型的屬性和方法。

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章