Swift 中的泛型

Swift泛型介紹

泛型是為Swift編程靈活性的一種語法,在函數(shù)、枚舉、結(jié)構(gòu)體、類中都得到充分的應(yīng)用,它的引入可以起到占位符的作用,當(dāng)類型暫時(shí)不確定的,只有等到調(diào)用函數(shù)時(shí)才能確定具體類型的時(shí)候可以引入泛型。
我們之前實(shí)際上已經(jīng)使用過泛型,例如:Swift的Array和Dictionary類型都是泛型集。

你可以創(chuàng)建一個(gè)Int數(shù)組,也可創(chuàng)建一個(gè)String數(shù)組,或者甚至于可以是任何其他Swift的類型數(shù)據(jù)數(shù)組。同樣的,你也可以創(chuàng)建存儲任何指定類型的字典(Dictionary),而且這些類型可以是沒有限制的。

我們?yōu)槭裁匆褂梅盒湍??下面有個(gè)例子可以簡單說明使用泛型的好處

// 定義一個(gè)函數(shù),要求追加數(shù)組數(shù)據(jù)到指定一個(gè)數(shù)組中
func appendIntToArray(src:[Int],inout dest:[Int]) {
    // 遍歷并加到數(shù)組后邊
    for element in src {
        dest.append(element)
    }
}

// 使用appendIntToArray添加整形數(shù)組數(shù)據(jù)
var arr = [2,5]
appendIntToArray([12,9], dest: &arr)

print(arr)  // [2,5,12,9]
// 那么再要求讓你實(shí)現(xiàn)添加字符串呢,好吧重寫一個(gè)
func appendStringToArray(src:[String],inout dest:[String]) {
    for element in src {
        dest.append(element)
    }
}

var strArr = ["OC","Swift"]
appendStringToArray(["PHP", "C#"], dest: &strArr)
print(strArr)
// 如果有需要你實(shí)現(xiàn)添加其他類型呢?
// 是不是每個(gè)類型都需要寫一個(gè)對應(yīng)的函數(shù)去實(shí)現(xiàn),那這樣就太復(fù)雜了!這時(shí)候我們就需要使用泛型
// 定義泛型函數(shù),在普通函數(shù)名后面加上<T>,T是個(gè)類型占用符,可以表示任何類型

func appendArray<T>(src:[T],inout dest:[T]) {
    for element in src {
        dest.append(element)
    }
}

// 看到如此強(qiáng)大了吧?然后隨意使用
var arr2 = [5,8]
appendArray([9,58], dest: &arr2)  // appendArray自動識別要添加的數(shù)組數(shù)據(jù)類型
print(arr2)                       // [5, 8, 9, 58]

var strArr2 = ["renhairui","hello"]
appendArray(["nihao", "helloworld"], dest: &strArr2)
print(strArr2)                    // ["renhairui", "hello", "nihao", "helloworld"]

var doubleArr = [1.2,3.4]
appendArray([6.5,1.0], dest: &doubleArr)
print(doubleArr)                  // [1.2, 3.4, 6.5, 1.0]

我的理解:泛型就是先占坑,具體占坑做什么,隨你

Swift泛型使用

Swift泛型相關(guān)使用可分為以下幾點(diǎn):
泛型函數(shù)
泛型類型
泛型約束
泛型協(xié)議

泛型函數(shù),函數(shù)參數(shù)或返回值類型用泛型表示

// 泛型函數(shù)定義式
func 函數(shù)名<泛型1,泛型2,…>(形參列表)->返回值類型
{
// 函數(shù)體...
}

泛型函數(shù)使用實(shí)例

// 定義一個(gè)泛型函數(shù),把2個(gè)參數(shù)的值進(jìn)行交換
func swapTwoValues<T>(inout valueOne:T ,inout valueTwo:T) {
    let temporaryA = valueOne
    valueOne = valueTwo
    valueTwo = temporaryA
}

var oneInt = 3
var twoInt = 107
swapTwoValues(&oneInt, valueTwo: &twoInt)
print("oneInt = \(oneInt), twoInt = \(twoInt)")  // oneInt = 107, twoInt = 3

var oneStr = "Hello"
var twoStr = "World"
swapTwoValues(&oneStr, valueTwo: &twoStr)
print("oneStr = \(oneStr), twoStr = \(twoStr)")  // oneStr = world, twoStr = hello

泛型類型,在定義類型時(shí)使用泛型

使用也和泛型函數(shù)差不多,就是在類型名后面加上<泛型1,泛型2,…>,然后在類型里面直接使用泛型即可

通常在泛型函數(shù)中,Swift 允許你定義你自己的泛型類型。這些自定義類、結(jié)構(gòu)體和枚舉作用于任何類型,如同Array和Dictionary的用法。
這部分向你展示如何寫一個(gè)泛型集類型--Stack(棧)。一個(gè)棧是一系列值域的集合,和Array(數(shù)組)類似,但其是一個(gè)比 Swift 的Array類型更多限制的集合。一個(gè)數(shù)組可以允許其里面任何位置的插入/刪除操作,而棧,只允許在集合的末端添加新的項(xiàng)(如同push一個(gè)新值進(jìn)棧)。同樣的一個(gè)棧也只能從末端移除項(xiàng)(如同pop一個(gè)值出棧)。

注意 棧的概念已被UINavigationController類使用來模擬視圖控制器的導(dǎo)航結(jié)構(gòu)。
你通過調(diào)用UINavigationController的pushViewController(:animated:)方法來為導(dǎo)航棧添加(add)新的視圖控制器;
而通過popViewControllerAnimated(
:)的方法來從導(dǎo)航棧中移除(pop)某個(gè)視圖控制器。
每當(dāng)你需要一個(gè)嚴(yán)格的后進(jìn)先出方式來管理集合,堆棧都是最實(shí)用的模型。

下圖展示了一個(gè)棧的壓棧(push)/出棧(pop)的行為:


現(xiàn)在有三個(gè)值在棧中;
第四個(gè)值“pushed”到棧的頂部;
現(xiàn)在有四個(gè)值在棧中,最近的那個(gè)在頂部;
棧中最頂部的那個(gè)項(xiàng)被移除,或稱之為“popped”;
移除掉一個(gè)值后,現(xiàn)在棧又重新只有三個(gè)值。

這里展示了如何寫一個(gè)非泛型版本的棧,Int值型的棧:

// 這里展示了如何寫一個(gè)非泛型版本的棧,Int值型的棧:
struct IntStack {
    var items = [Int]()
    mutating func push(item:Int) {
        items.append(item)
    }
    
    mutating func pop() -> Int {
        return items.removeLast()
    }

}

這個(gè)結(jié)構(gòu)體在棧中使用一個(gè)Array性質(zhì)的items存儲值。Stack提供兩個(gè)方法:push和pop,從棧中壓進(jìn)一個(gè)值和移除一個(gè)值。這些方法標(biāo)記為可變的,因?yàn)樗鼈冃枰薷模ɑ蜣D(zhuǎn)換)結(jié)構(gòu)體的items數(shù)組。
上面所展現(xiàn)的IntStack類型只能用于Int值,不過,其對于定義一個(gè)泛型Stack類(可以處理任何類型值的棧)是非常有用的。

這里是一個(gè)相同代碼的泛型版本

// 這里是一個(gè)相同代碼的泛型版本:
struct Stack<T> {
    var items = [T]()
    mutating func push(item:T) {
        items.append(item)
    }
    
    mutating func pop() -> T {
        return items.removeLast()
    }
}

注意到Stack的泛型版本基本上和非泛型版本相同,但是泛型版本的占位類型參數(shù)為T代替了實(shí)際Int類型。這種類型參數(shù)包含在一對尖括號里(<T>),緊隨在結(jié)構(gòu)體名字后面。
T定義了一個(gè)名為“某種類型T”的節(jié)點(diǎn)提供給后來用。這種將來類型可以在結(jié)構(gòu)體的定義里任何地方表示為“T”。在這種情況下,T在如下三個(gè)地方被用作節(jié)點(diǎn):

  • 創(chuàng)建一個(gè)名為items的屬性,使用空的T類型值數(shù)組對其進(jìn)行初始化;
  • 指定一個(gè)包含一個(gè)參數(shù)名為item的push(_:)方法,該參數(shù)必須是T類型;
  • 指定一個(gè)pop方法的返回值,該返回值將是一個(gè)T類型值。

由于Stack是泛型類型,所以在 Swift 中其可以用來創(chuàng)建任何有效類型的棧,這種方式如同Array和Dictionary。
你可以通過在尖括號里寫出棧中需要存儲的數(shù)據(jù)類型來創(chuàng)建并初始化一個(gè)Stack實(shí)例。比如,要創(chuàng)建一個(gè)strings的棧,你可以寫成Stack<String>():

var stackOfStrings = Stack<String>()
stackOfStrings.push("uno")
stackOfStrings.push("dos")
stackOfStrings.push("tres")
stackOfStrings.push("cuatro")
// 現(xiàn)在棧已經(jīng)有4個(gè)string了

下圖將展示stackOfStrings如何push這四個(gè)值進(jìn)棧的過程:


從棧中pop并移除值"cuatro"

let fromTop = stackOfStrings.pop()
// fromTheTop 等于 "cuatro", 現(xiàn)在棧中還有3個(gè)string

下圖展示了如何從棧中pop一個(gè)值的過程:


擴(kuò)展一個(gè)泛型類型

當(dāng)你擴(kuò)展一個(gè)泛型類型的時(shí)候,你并不需要在擴(kuò)展的定義中提供類型參數(shù)列表。更加方便的是,原始類型定義中聲明的類型參數(shù)列表在擴(kuò)展里是可以使用的,并且這些來自原始類型中的參數(shù)名稱會被用作原始定義中類型參數(shù)的引用。

下面的例子擴(kuò)展了泛型Stack類型,為其添加了一個(gè)名為topItem的只讀計(jì)算屬性,它將會返回當(dāng)前棧頂端的元素而不會將其從棧中移除。

extension Stack {
    var topItem:T? {
        return items.isEmpty ? nil :items[items.count - 1]
    }
}

if let topItem = stackOfStrings.topItem {
    print("The top item on the stack is \(topItem).")
}
// 輸出 "The top item on the stack is tres."

泛型約束,為泛型類型添加約束

泛型約束大致分為以下幾種:
繼承約束,泛型類型必須是某個(gè)類的子類類型
協(xié)議約束,泛型類型必須遵循某些協(xié)議
條件約束,泛型類型必須滿足某種條件
約束的大概使用格式

// 繼承約束使用格式
func 函數(shù)名<泛型: 繼承父類>(參數(shù)列表) -> 返回值 {
// 函數(shù)體,泛型類型是某個(gè)類的子類類型
}
// 協(xié)議約束使用格式
func 函數(shù)名<泛型: 協(xié)議>(參數(shù)列表) -> 返回值 {
// 函數(shù)體,泛型類型遵循某些協(xié)議
}
// 條件約束使用格式
func 函數(shù)名<泛型1, 泛型2 where 條件>(參數(shù)列表) -> 返回值 {
// 函數(shù)體,泛型類型滿足某些條件

}

繼承約束使用范例

// 繼承約束使用范例
// 定義一個(gè)父類,動物類
class Animal {
    // 動物都會跑
    func run() {
        print("Animal run")
    }
}

class Dog :Animal {
    override func run() {  // 重寫父類方法
        print("Dog run")
    }
}

class Cat :Animal {
    override func run() {
         print("Cat run")
    }
}

// 定義泛型函數(shù),接受一個(gè)泛型參數(shù),要求該泛型類型必須繼承Animal
func AnimalRunPint<T:Animal>(animal:T) {
    animal.run()  // 繼承了Animal類的子類都有run方法可以調(diào)用
}

AnimalRunPint(Dog())   // Dog run
AnimalRunPint(Cat())   // Cat run

協(xié)議約束使用范例
Swift標(biāo)準(zhǔn)庫中定義了一個(gè)Equatable協(xié)議,該協(xié)議要求任何遵循的類型實(shí)現(xiàn)等式符(==)和不等符(!=)對任何兩個(gè)該類型進(jìn)行比較。所有的Swift標(biāo)準(zhǔn)類型自動支持Equatable協(xié)議。

// 協(xié)議約束使用范例

// 定義泛型函數(shù),為泛型添加協(xié)議約束,泛型類型必須遵循Equatable協(xié)議
func findIndex<T: Equatable>(array: [T], valueToFind: T) -> Int? {
    var index = 0
    for value in array {
        if value == valueToFind { // 因?yàn)樽裱薊quatable協(xié)議,所以可以進(jìn)行相等比較
            return index
        } else {
            index++
        }
    }
    return nil
}

// 在浮點(diǎn)型數(shù)組中進(jìn)行查找,Double默認(rèn)遵循了Equatable協(xié)議
let doubleIndex = findIndex([3.14159, 0.1, 0.25], valueToFind: 9.3)
if let index = doubleIndex {
    print("在浮點(diǎn)型數(shù)組中尋找到9.3,尋找索引為\(index)")
} else {
    print("在浮點(diǎn)型數(shù)組中尋找不到9.3")
}

// 在字符串?dāng)?shù)組中進(jìn)行查找,String默認(rèn)遵循了Equatable協(xié)議
let stringIndex = findIndex(["Mike", "Malcolm", "Andrea"], valueToFind: "Andrea")
if let index = stringIndex {
    print("在字符串?dāng)?shù)組中尋找到Andrea,尋找索引為\(index)")
} else {
    print("在字符串?dāng)?shù)組中尋找不到Andrea")
}

/* 打印:
在浮點(diǎn)型數(shù)組中尋找不到9.3
在字符串?dāng)?shù)組中尋找到Andrea,尋找索引為2
*/

泛型協(xié)議和條件約束

上面的Equatable協(xié)議實(shí)際上不是普通的協(xié)議,而是泛型協(xié)議,假設(shè)泛型類型必須遵循一個(gè)協(xié)議,此時(shí)就必須在協(xié)議中引入一個(gè)關(guān)聯(lián)類型來解決。

// 定義一個(gè)泛型協(xié)議,和其他泛型使用方式不同,這里泛型是以關(guān)聯(lián)類型形式使用的

protocol Stackable{
    // 聲明一個(gè)關(guān)聯(lián)類型,使用typealias關(guān)鍵字
    typealias ItemType
    mutating func push(item:ItemType)
    mutating func pop() -> ItemType
}


struct Stack<T>:Stackable{
    var store = [T]()
    mutating func push(item:T){  // 實(shí)現(xiàn)協(xié)議的push方法要求
        store.append(item)
    }
    mutating func pop() -> T {   // 實(shí)現(xiàn)協(xié)議的pop方法要求
        return store.removeLast()
    }
}

// 創(chuàng)建Stack結(jié)構(gòu)體,泛型類型為String
var stackOne = Stack<String>()
stackOne.push("hello")
stackOne.push("swift")
stackOne.push("world")
let t = stackOne.pop()
print("t = \(t)") //結(jié)果:t = world

// 添加泛型條件約束,C1和C2必須遵循Stackable協(xié)議,而且C1和C2包含的泛型類型要一致
func pushItemOneToTwo<C1: Stackable, C2: Stackable
    where C1.ItemType == C2.ItemType>(inout stackOne: C1, inout stackTwo: C2)
{ // 因?yàn)镃1和C2都遵循了Stackable協(xié)議,才有ItemType屬性可以調(diào)用
    let item = stackOne.pop()
    stackTwo.push(item)
}

// 定義另外一個(gè)結(jié)構(gòu)體類型,同樣實(shí)現(xiàn)Stackable協(xié)議,實(shí)際上里面的實(shí)現(xiàn)和Stack一樣
struct StackOther<T>: Stackable{
    var store = [T]()
    mutating func push(item:T){  // 實(shí)現(xiàn)協(xié)議的push方法要求
        store.append(item)
    }
    mutating func pop() -> T {   // 實(shí)現(xiàn)協(xié)議的pop方法要求
        return store.removeLast()
    }
}

// 創(chuàng)建StackOther結(jié)構(gòu)體,泛型類型為String
var stackTwo = StackOther<String>()
stackTwo.push("where")

// 雖然stackOne和stackTwo類型不一樣,但泛型類型一樣,也同樣遵循了Stackable協(xié)議
pushItemOneToTwo(&stackOne, stackTwo: &stackTwo )
print("stackOne = \(stackOne.store), stackTwo = \(stackTwo.store)")
// 打印:stackOne = ["hello"], stackTwo = ["where", "swift"]
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 本章將會介紹 泛型所解決的問題泛型函數(shù)類型參數(shù)命名類型參數(shù)泛型類型擴(kuò)展一個(gè)泛型類型類型約束關(guān)聯(lián)類型泛型 Where...
    寒橋閱讀 713評論 0 2
  • 什么時(shí)候需要使用泛型 在講到泛型之前,先寫一段代碼(文中的代碼都是Swift書寫)。 這是一個(gè)很常見的也很簡單的I...
    BennyLoo閱讀 3,098評論 1 4
  • 泛型(Generics) 泛型代碼允許你定義適用于任何類型的,符合你設(shè)置的要求的,靈活且可重用的 函數(shù)和類型。泛型...
    果啤閱讀 758評論 0 0
  • 作者:Thomas Hanning,原文鏈接,原文日期:2015/09/09譯者:pmst;校對:numbbbbb...
    梁杰_numbbbbb閱讀 466評論 0 4
  • 這幾天月底,在單位忙得團(tuán)團(tuán)轉(zhuǎn),到了家就懶得動了,簡書也有幾天沒有更新了。今天是周五,感覺人也放松了好多,趕緊把前幾...
    欒曉君閱讀 1,600評論 13 20

友情鏈接更多精彩內(nèi)容