Swift中關於泛型的一些注意點

閱讀本文須要對 Swift 基本語法和泛型有基本的瞭解。
本文約840字,閱讀大概須要 10 min。
收穫:對 Swift 中的泛型使用更加深刻。
複製代碼

儘可能使用泛型來替代 Any

假設,在項目中咱們須要實現一個 Stack 的數據結構,而且想支持任意基本類型。那麼,咱們可使用 Any 實現下面的代碼:objective-c

struct Stack {
    var data = [Any]()
    
    init(data: [Any]) {
        self.data = data
    }
    
    mutating func pop() {
        data.removeLast()
    }
    
    mutating func push(element: Any) {
        data.append(element)
    }
}

var stack = Stack(data: ["swift", "objective-c"])
stack.push(element: "iOS")
複製代碼

上述代碼能夠知足咱們的需求,它能夠支持任意類型。可是這種寫法在使用中仍是有不少缺點的:算法

  • 使用元素時須要解包、轉類型
print(stack.data.first as! String)
複製代碼
  • 由於是 Any,因此能夠賦值任意類型,致使代碼可讀性和健壯性不高
stack.push(element: 13)
//Error - Could not cast value of type 'Swift.Int' (0x10b4d7070) to 'Swift.String' (0x10b4d9190)
print(stack.data.last as! String) 
複製代碼

因此,遇到此類需求時,咱們應該使用泛型來代替上面的 Any。swift

struct Stack<Element> {
    var data = [Element]()
    
    init(data: [Element]) {
        self.data = data
    }
    
    mutating func pop() {
        data.removeLast()
    }
    
    mutating func push(element: Element) {
        data.append(element)
    }
}

var stack = Stack(data: ["swift", "objective-c"])
stack.push(element: "iOS")
複製代碼
  • 只需解包無須轉變類型
print(stack.data.first!)
複製代碼
  • 使用時已肯定類型,賦值別的類型直接報錯,從而提升代碼的健壯性
// 編譯不成功 - Cannot convert value of type 'Int' to expected argument type 'String'
stack.push(element: 13)
複製代碼

經過 泛型 和 Extension 實現通用算法

接着上面的例子,假如咱們想在 Stack 的基礎上,添加一個計算棧內全部數字類型元素的和,咱們能夠用如下代碼實現:數組

extension Stack where Element: Numeric {
    func sum() -> Element {
        var total: Element = 0
        
        for num in data {
            total += num
        }
        return total
    }
}

var numStack = Stack(data: [1, 2])
numStack.sum() // 3
// strStack 是沒法調用 sum 函數的,由於String 並無遵照 Numberic 協議
var strStack = Stack(data: ["Swift", "iOS13"])
複製代碼

extension Stack where Element: Numeric 的意思是指:只有 Stack 的元素遵照 Numberic 協議,才能調用 sum() 函數。這麼作是由於非數字類型是沒法進行累加操做的。若是咱們不添加該限制的話,total += num 這句代碼會報錯:Binary operator '+=' cannot be applied to two 'Element' operandsbash

經過泛型 和 Extension 爲 Protocol 擴展方法

map(系統標準庫)

咱們能夠經過 泛型 + Extension 爲 Protocol 擴展兼容多類型的方法,好比標準庫提供的 map 函數,它的實現就是藉助 泛型 + Extension 實現的:數據結構

extension Collection {
    func map<T>(_ transform: (Element) -> T) -> [T] {
        var result = [T]()
        
        result.reserveCapacity(self.count)
        var position = startIndex
        while position != endIndex {
            result.append(transform(self[position]))
            
            position = index(after: position)
        }
        return result
    }
}
複製代碼

由於在調用 map() 函數時,咱們能夠肯定參數計算出 result 的大小,因此咱們可使用 result.reserveCapacity(self.count) 來優化代碼效率。關於 reserveCapacity 更加詳細的內容,請參見 Swift 中的 Array 性能比較: append vs reserveCapacity(譯)app

shuffle(系統標準庫)

Fisher–Yates shuffle 算法是一個用來將一個有限集合生成一個隨機排列的算法(數組隨機排序)。dom

extension RandomAccessCollection where Self: MutableCollection {
    mutating func shuffle() {
        let n = count
        guard n > 1 else { return }
        
        for (i, pos) in indices.dropLast().enumerated() {
            let otherPos = index(startIndex, offsetBy: Int.random(in: i..<n))
            swapAt(pos, otherPos)
        }
    }
}

var arr = [1,2,3,4,5,6,7]
arr.shuffle()
//[4, 3, 5, 1, 6, 7, 2]
print(arr)
複製代碼

參考

相關文章
相關標籤/搜索