Swift 5.1 (21) - 泛型

級別: ★☆☆☆☆
標籤:「iOS」「Swift 5.1 」「泛型」
做者: 沐靈洛
審校: QiShare團隊
php


使用泛型能讓咱們寫出靈活的,可複用的函數和類型,這些函數和類型會根據咱們定義的要求與任何類型一塊兒使用。使用泛型咱們不只能夠避免重複的代碼並且能以更加清晰抽象的方式表達代碼意圖。git

泛型是Swift最強大的特徵之一,而且許多Swift的標準庫都是使用泛型的代碼編譯的。github

泛型解決的問題

func swapTwoInts(_ a: inout Int, _ b: inout Int) {
    let temporaryA = a
    a = b
    b = temporaryA
}
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
}
複製代碼

上述三個函數,分別在交換對應類型的兩個值。但迴歸函數的本質,其實這三個函數的主體是同樣的,只不過他們分別接受IntDoubleString三種類型做爲函數的入參。 基於此編寫一個函數,能交換任何類型的兩個值,會更有用,也更靈活。編程

泛型函數

//MARK:編寫泛型函數
func swapTwoValues<T>(_ a : inout T, _  b : inout T) {
    let temp = a
    a = b
    b = temp
}
//調用
var a = "Qishare"
var b = "Come On"
swapTwoValues(&a, &b)
print(a,b)//!< Come On Qishare
複製代碼

泛型函數中會使用佔位符類型代替實際的類型名稱,如IntStringDouble等。本例中此佔位類型的名稱爲T,此佔位符類型名稱並未指定T究竟是什麼,而是表示不管T表明什麼類型,ab都必須具有相同的T類型。每次調用swapTwoValues(_:_:)函數時,Swift都須要進行類型推斷,肯定代替T使用的實際類型。swift

泛型函數與非泛型函數的區別在於,編寫泛型函數時,函數的名稱後須要使用尖括號< >,並在其中指定佔位符類型的名稱:<T><>用以告訴Swift函數T是此函數定義的佔位符類型名稱。由於T是一個佔位符類型,因此Swift不會查找T的實際類型。數組

類型參數

類型參數 :泛型函數調用時,能夠被函數實際類型代替的參數。在泛型函數名稱後尖括號中指定並命名後,意味着指定了類型參數,咱們即可以使用此類型參數來定義函數的參數,函數的返回值類型。固然也能夠採用<T,Q,...>的形式定義多個類型參數。bash

命名類型參數

在大多數狀況下,類型參數命名是具備描述性的,例如Dictionary <Key,Value>中的KeyValue以及Array <Element>中的Element,它向咱們展現了類型參數與咱們所使用的泛型類型或泛型函數之間的關係。可是,當它們之間沒有有意義的關係時,一般會使用單個字母(例如TUV)來命名它們。微信

注意:請始終爲類型參數提供駝峯式的大寫名稱(例如TMyTypeParameter),以代表它們是類型的佔位符,而不是值。閉包

泛型類型

除了泛型函數,Swift還容許咱們能定義本身的泛型類型,涵蓋類,結構體,枚舉類型,並能夠與任何類型一塊兒使用。和字典或數組類似。app

接下來咱們將定義一個棧的結構體類型,命名爲Stack,定義Stack類型以前,咱們須要知道棧結構的特色是:先入後出,後入先出。 1.定義只能存儲特定類型的棧

struct Stack {
    var items = [Int]()
    mutating func push(_ item:Int){
        items.append(item)
    }
    mutating func pop(_ item:Int) -> Int {
       return items.removeLast()
    }
}
//調用
var stack_int = Stack()
stack_int.push(7)
stack_int.push(3)
stack_int.push(2)
print(stack_int)//Stack(items: [7, 3, 2])
複製代碼

如若我須要存儲其餘類型呢? 2.定義泛型類型Stack

struct Stack<Element> {
    var items = [Element]()
    mutating func push(_ item:Element){
        items.append(item)
    }
    mutating func pop(_ item:Element) -> Element {
        return items.removeLast()
    }
}
//調用
var stack_int = Stack<Int>()
stack_int.push(7)
stack_int.push(3)
stack_int.push(2)
print(stack_int)
var stack_string = Stack<String>()
stack_string.push("QISHARE")
print(stack_string)
複製代碼

注意:泛型類型Stack具備一個稱爲Element的類型參數,而不是Int的實際類型。Element是爲此泛型類型定義的佔位符類型,在結構體定義中的任何位置均可以使用Element來引用將來調用時的實際類型。

泛型類型的擴展

當擴展一個泛型類型的時候,咱們不須要提供類型參數的列表做爲此擴展定義的一部分。由於,定義泛型類型時定義好的類型參數在其擴展中依舊時可用的。

extension Stack {
    var topItem : Element? {
        items.last
    }
}
//調用
var stack_string = Stack<String>()
stack_string.push("QISHARE")
if let topItem = stack_string.topItem {
   print(topItem)//!< QISHARE
}
複製代碼

類型約束

泛型函數和泛型類型雖然能夠與任何類型一塊兒使用,可是有時咱們須要強制限制能夠一塊兒使用的類型,這個時候就須要使用類型約束。好比:Swift中DictionaryKey便被約束爲必須遵照hashable協議。 類型約束:指定類型參數必須繼承自特定的類、遵照某個協議或協議組合。

類型約束的語法

語法:參數類型定義時,參數名稱後放置單獨的類或協議約束,約束與 參數名稱之間使用冒號:隔開。 注意:類型的約束條件只能爲類或協議。

func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
    // `T`約束爲繼承自`SomeClass`的類型 `U`約束爲遵照`SomeProtocol` 協議的類型
}
複製代碼

類型約束的使用

func findIndex<T>(of valueToFind: T, in array:[T]) -> Int? {
    for (index, value) in array.enumerated() {
        if value == valueToFind {
            return index
        }
    }
    return nil
}
複製代碼

上述代碼編譯時會出現出錯:Binary operator '==' cannot be applied to two 'T' operands(操做數)。由於==操做符在Swift中不是全部類型都支持。好比,咱們自定義的類型,只有只有實現了Swift標準庫定義的Equatable協議,才能運用==!=來比較該類型的任意的兩個值。所以正確的寫法是須要添加類型約束的:

func findIndex<T : Equatable>(of valueToFind: T, in array:[T]) -> Int? {
    for (index, value) in array.enumerated() {
        if value == valueToFind {
            return index
        }
    }
    return nil
}
複製代碼

類型關聯

當咱們定義協議時,有時聲明一個或多個關聯類型做爲協議定義的一部分是頗有用的。 關聯類型的做用,主要提供某個類型的佔位名稱,而後做爲協議的一部分去使用。關聯類型的實際使用類型直到協議被實現時纔會指定。關聯類型使用關鍵字associatedtype指定。

類型關聯的使用

//定義協議使用類型關聯
protocol Container {
    associatedtype Item
    mutating func append(_ item : Item)
    var count : Int{get}
    subscript(i:Int)->Item{get}
    
}
//定義整型Stack類型
struct IntStack : Container {
    var items = [Int]()
    mutating func push(_ item:Int){
        items.append(item)
    }
    mutating func pop(_ item:Int) -> Int {
        return items.removeLast()
    }
    //實現協議時,須要明確關聯類型的實際類型
    typealias Item = Int //!< ①

    mutating func append(_ item: Item) {//!< ①若不存在,此處可直接 Int
        push(item)
    }
    
    var count: Int {
        items.count
    }
    subscript(i: Int) -> Int {
        items[i]
    }
}
複製代碼

Typealias Item = Int是針對Container協議的實現,將Item的抽象類型轉換爲Int的具體類型。基於Swift的類型推斷,經過append(_ :)方法即可以推斷出Item的類型以及下標返回值的類型。 採用關聯類型做爲協議定義的一部分時,此協議也能夠被泛型類型實現。

struct Stack<Element> : Container {
    var items = [Element]()
    mutating func push(_ item:Element){
        items.append(item)
    }
    mutating func pop(_ item:Element) -> Element {
        return items.removeLast()
    }
    //實現協議
    typealias Item = Element
    //自動提示爲`Element`
    mutating func append(_ item: Element) {
        push(item)
    }
    var count: Int {
        items.count
    }
    subscript(i: Int) -> Element {
        items[i]
    }
}
複製代碼

擴展示有類型以指定關聯類型

上篇協議中咱們知道,當特定類型已經實現了協議的要求,但還沒有聲明該類型遵照協議。能夠經過擴展聲明該類型遵照此協議。當協議中定義了關聯類型一樣也是能夠的。

好比:SwiftArray類型已經提供了Container協議中方法,屬性要求的實現,徹底匹配Container協議要求。這意味着咱們經過Array的擴展聲明Array遵照Container協議,而且Array內部對於協議要求的實現能夠推斷出協議關聯類型Item的實際類型。

extension Array : Container{}
//擴展示有類型以指定關聯類型?是否成功。
extension Array : Container{
    func associateTypeOne(_:Item){}
    func associateTypeTwo(_:Element){}
    func associateTypeThree(_ : Self){}//實現協議時,Self都會與協議實現類型進行關聯
}
複製代碼

值得注意的是:在咱們定義這個擴展以後,we can use any Array as a Container ? 實際上此處知識點還需本身探索一番。 若咱們有一個具體未使用關聯類型的協議Int_Container

protocol Int_Container {
    mutating func append(_ item : Int)
    var count : Int{get}
    subscript(i:Int)->Int{get}
}
複製代碼
  1. 定義函數,參數爲協議類型。
func testProtocolWithAssociateTypeOne(_ parameter : Container) {
/*報錯:Protocol 'Container' can only be used as a generic 
constraint because it has Self or associated type requirements*/
}
func testProtocolNoAssociatetype(_ parameter : Int_Container){
    //編譯成功
}
複製代碼

2.使用isas判斷某個類型是否遵照特定協議

let array : [Any] = [1,"ddd",3]
if array is Container {
 /*報錯:Protocol 'Container' can only be used as a generic 
constraint because it has Self or associated type requirements*/
    print("遵照此協議")
} else {
    print("不遵照此協議")
}
if array is Int_Container {
    print("遵照此協議")
} else {
    print("不遵照此協議")
}
複製代碼

上述1,2的示例中,帶有關聯類型的協議,不論是做爲函數的參數類型或對象的屬性類型,仍是單獨判斷某個類型是否遵照此協議,都會報錯:Protocol 'Container' can only be used as a generic constraint because it has Self or associated type requirements。編譯器告訴咱們Container協議有Self或關聯類型的要求,所以它只能被用來做爲泛型的約束。 關於Self的提示:系統庫爲協議提供了Self關聯類型,默認指向了實現此協議的類型。

//系統的`Equatable `協議
public protocol Equatable {
    static func == (lhs: Self, rhs: Self) -> Bool
}
//實際實現
class Person : Equatable {
    //默認關聯`Self`到`Person`
    static func == (lhs: Person, rhs: Person) -> Bool {
        return lhs.name == rhs.name
    }
    var name : String?
    var age : String?
}
複製代碼

若咱們的Int_Container協議定義中使用了關聯類型Self,編譯器依舊會報此錯誤。

protocol Int_Container {
    mutating func append(_ item : Int)
    var count : Int{get}
    subscript(i:Int)->Int{get}
    static func testCompare(l:Self,r:Self)->Bool
}
複製代碼

對比泛型和協議的關聯類型:

  • 泛型:使用佔位符類型完成泛型類型方法的實現,泛型的實際類型由使用此泛型類型者指定。即:使用時指定實際類型。
  • 關聯類型:使用佔位符類型完成協議方法的定義,關聯類型的實際類型由實現此協議者指定,即:實現時指定實際類型。

關聯類型的協議用做泛型的約束舉例:

//①
struct TempStruct<T:Container> {
    let title : String = "關聯類型的協議用做泛型類型的約束:代替`T`的實際類型必須遵照`Container`協議"
    func showYourMagic(_ para : T) -> Void {
        print(para)
    }
}
//②
func showYourMagic<T>(_ para : T) -> Void {
    print(para)
}
showYourMagic("展現魔法")
複製代碼

總結:帶有關聯類型的協議只能用做泛型的約束。

添加約束到關聯類型

能夠爲協議中的關聯類型添加類型約束,以要求符合條件的類型知足這些約束。

protocol Container {
    associatedtype Item : Equatable
    mutating func append(_ item : Item)
    var count : Int{get}
    subscript(i:Int)->Item{get}
}
複製代碼

在關聯類型的約束中使用協議

在關聯類型的約束中使用協議,協議能夠做爲協議要求的一部分出現。(當前協議可做爲關聯類型的協議要求出現)。 以Container協議舉例,定義協議SuffixableContainer繼承自Container,實現功能:實現此協議類型的實例,須要截取它後綴必定長度,組成新的實例。

//協議定義
//定義繼承協議
protocol SuffixableContainer : Container {
    
    /*新構建的關聯類型`suffix`約束條件有兩個:
     1.實現此協議時指定的`suffix`的類型必須是實現`SuffixableContainer`協議的類型
     2.此`suffix`佔位的容器類型的存儲項類型`Item`必須與當前實現此協議的存儲項保持一致。
     */
    associatedtype suffix : SuffixableContainer where suffix.Item == Item
    
    /*`item`關聯類型的實際類型由泛型類型的佔位類型決定。
     此方法必須確保`String`類型的容器,截取的後綴,重組後的容器仍然是`String`類型的*/
    func suffix(_ size : Int) -> suffix
    
}
//實現
extension Stack : SuffixableContainer {
    func suffix(_ size: Int) -> Stack {
        var result = Stack()
        for index in (count-size)..<count {
            result.append(self[index])
        }
        return result
    }
}
//調用
var stack_int = Stack<Int>()
stack_int.push(7)
stack_int.push(3)
stack_int.push(2)
stack_int.append(4)
let suffix_int = stack_int.suffix(3)
print(stack_int,suffix_int)//3 2 4
複製代碼

上述示例,SuffixableContainer協議的關聯類型suffix使用了SuffixableContainer協議進行約束。 基於suffix(_ : ) -> suffix此方法必須確保String類型的特定容器,截取的後綴,重組後的容器仍然是String類型的此容器。解釋一下關於關聯類型suffix的約束:

  • 實現此協議時指定的suffix的類型必須是實現SuffixableContainer協議的類型。
  • suffix佔位的容器類型的存儲項類型Item必須與當前實現此協議類型(調用類型)的存儲項保持一致。item關聯類型的實際類型由泛型類型的佔位類型決定。

泛型的where閉包

where閉包能要求關聯類型必須遵照某個特定的協議,或特定的類型參數與關聯類型必須相等。where閉包以where關鍵字開始,後跟關聯類型的約束或類型參數與關聯類型之間的相等關係。咱們能夠在類型或函數主體的大括號前寫一個通用的where子句來設置咱們的約束。 以匹配兩個容器是否相等的功能舉例來闡述。

func twoContainerIsEqual<C1:Container,C2:Container>(_ someContainer : C1 , _ anotherContainer : C2) -> Bool where C1.Item == C2.Item , C2.Item : Equatable {
    /*where閉包對於關聯類型的約束:1.容器元素類型一致,
      2.元素的類型遵照`Equatable`協議*/
    if someContainer.count != anotherContainer.count {
        return false
    }
    for i in 0..<someContainer.count {
        if someContainer[i] != anotherContainer[i] {
            return false
        }
    }
    return true
}
複製代碼

使用where閉包擴展泛型

1.where閉包能夠做爲泛型擴展的一部分。

extension Stack where Element: Equatable {
    func isTop(_ item: Element) -> Bool {
        guard let topItem = items.last else {
            return false
        }
        return topItem == item
    }
}
//調用
struct NotEquatable {
}
var notEquatableStack = Stack<NotEquatable>()
let notEquatableValue = NotEquatable()
notEquatableStack.push(notEquatableValue)

/* Error:Argument type 'NotEquatable' 
 does not conform to expected type 'Equatable'*/
notEquatableStack.isTop(notEquatableValue)
複製代碼

2.where閉包能夠做爲協議擴展的一部分。

/*
協議經過擴展能夠爲遵照協議的類型提供方法,初始化,下
標和計算屬性的實現。這一點容許咱們爲協議自己定義行
爲,而不是基於遵照協議的每一個類型
*/
extension Container where Item: Equatable {
//若`startsWith`函數名不與`container`中要求重名,則`startsWith`即是爲遵照此協議的類型增長了新的方法。
    func startsWith(_ item: Item) -> Bool {
        return count >= 1 && self[0] == item
    }
}
複製代碼

3.where閉包,能夠要求Container協議Item爲特定類型。

extension Container where Item == Double {
    func average() -> Double {
        var sum = 0.0
        for index in 0..<count {
            sum += self[index]
        }
        return sum / Double(count)
    }
}
複製代碼

關聯類型使用泛型 where閉包。

關聯類型上使用泛型where子句。 例如,爲Container 協議增長迭代器的功能。

protocol Container {
    associatedtype Item
    mutating func append(_ item: Item)
    var count: Int { get }
    subscript(i: Int) -> Item { get }
    
    associatedtype Iterator : IteratorProtocol where Iterator.Element == Item
    func makeIterator() -> Iterator
}
//構建迭代器
struct Iterator<T> : IteratorProtocol{
    
    var stack : Stack<T>
    var count = 0
    
    init(_ stack : Stack<T>) {
        self.stack = stack
    }
    
    typealias Element = T

    mutating func next() -> T? {
        let next = stack.count - 1 - count
        guard next >= 0 else {
            return nil
        }
        count += 1
        return stack[next]
    }
}
//咱們的泛型`Stack`須要實現`Sequence`協議
struct Stack<Element> : Container,Sequence {
    
    //container只能用做泛型約束。
    var items = [Element]()
    mutating func push(_ item:Element){
        items.append(item)
    }
    mutating func pop(_ item:Element) -> Element {
        return items.removeLast()
    }
    //實現協議
    typealias Item = Element
    //自動提示爲`Element`
    mutating func append(_ item: Element) {
        push(item)
    }
    var count: Int {
        items.count
    }
    subscript(i: Int) -> Element {
        items[i]
    }
    //迭代器的實現
    typealias IteratorType = Iterator<Element>
    func makeIterator() -> IteratorType {
        return Iterator.init(self)
    }
}
//調用
var stack_int = Stack<Int>()
stack_int.push(7)
stack_int.push(3)
stack_int.push(2)
stack_int.append(4)
for item in stack_int {
    print(item)
}
//輸出:
4
2
3
7
複製代碼

Iterator : IteratorProtocol where Iterator.Element == Item要求Iterator必須遍歷與容器的元素具備相同類型的元素,而無論迭代器的類型。

泛型下標

下標能夠泛型,也能夠包括泛型where子句,下標後的尖括號內寫佔位符類型名稱,並在下標正文的左花括號前寫泛型where子句。

extension Container {
    subscript<Indices: Sequence>(indices: Indices) -> [Item]
        where Indices.Iterator.Element == Int {
            var result = [Item]()
            for index in indices {
                result.append(self[index])
            }
            return result
    }
}
複製代碼

Indices.Iterator.Element == Int保證了序列中的索引與用於容器的索引具備相同的類型。即:意味着爲索引參數傳遞的值是整數序列。

參考資料: swift 5.1官方編程指南


瞭解更多iOS及相關新技術,請關注咱們的公衆號:

image

可添加以下小編微信,並備註加入QiShare技術交流羣,小編會邀請你加入《QiShare技術交流羣》。

小編微信

關注咱們的途徑有:
QiShare(簡書)
QiShare(掘金)
QiShare(知乎)
QiShare(GitHub)
QiShare(CocoaChina)
QiShare(StackOverflow)
QiShare(微信公衆號)

推薦文章:
WWDC2020(0623) 開發者大會觀看記錄
Swift 5.1 (20) - 協議
Swift 5.1 (19) - 擴展
Swift 5.1 (18) - 嵌套類型
Swift 5.1 (17) - 類型轉換與模式匹配
淺談編譯過程
深刻理解HTTPS 淺談 GPU 及 「App渲染流程」
iOS 查看及導出項目運行日誌
Flutter Platform Channel 使用與源碼分析
開發沒切圖怎麼辦?矢量圖標(iconFont)上手指南
DarkMode、WKWebView、蘋果登陸是否必須適配?
奇舞團安卓團隊——aTaller
奇舞週刊

相關文章
相關標籤/搜索