泛型詳解以及如何在Swift中應用方法或變數泛型?


在本教程中,您將學習如何使用泛型使您的程式碼在應用程式中更具可重用性和靈活性。您將透過示例瞭解如何建立可與任何型別一起使用的函式和屬性。

什麼是泛型?

Swift 允許您使用泛型編寫靈活且可重用的程式碼。泛型程式碼可以與任何型別一起使用。您可以編寫避免程式碼重複並以清晰、抽象的方式表達其意圖的程式碼。

泛型是 Swift 最強大的功能之一,Swift 標準庫的很大一部分都是使用泛型程式碼構建的。即使您沒有意識到,您在整個語言指南中都一直在使用泛型。

例如,Swift 的 Array 和 Dictionary 型別都是泛型集合。您可以建立一個儲存 Int 值的陣列或儲存 String 值的陣列。您可以構建幾乎任何可以在 Swift 中構建的型別的陣列。同樣,您可以建立一個字典來儲存任何指定型別的值,並且對該型別沒有任何限制。

為什麼我們需要使用泛型?

泛型的一個很好的用例示例是堆疊。堆疊是一個容器,它有兩個操作:“Push”用於向容器中新增專案,“Pull”用於從容器中刪除最後一個專案。第一步,我們在沒有泛型的情況下編寫堆疊。結果如下所示:

class IntStack {

   private var stackItems: [Int] = []
   
   func push(item: Int) {
      stackItems.append(item)
   }
   
   func pop() -> Int? {
      guard let lastItem = stackItems.last else {
         return nil
      }
      stackItems.removeLast()
      return lastItem
   }
}

該堆疊能夠處理整數。要構建一個可以處理字串的堆疊,我們應該怎麼做?這是一個非常不可靠的解決方案,因為我們必須在每個地方都替換“Int”。乍一看,以下解決方案似乎可行:

class AnyObjectStack {
   private var items: [AnyObject] = []
   func push(item: AnyObject) {
      items.append(item)
   }
   func pop() -> AnyObject? {
      guard let lastItem = items.last else {
         return nil
      }
      items.removeLast()
      return lastItem
   }
}

使用 AnyObject,我們現在可以將字串推入堆疊。在這種情況下,我們失去了型別安全,並且在使用堆疊時還必須進行大量的型別轉換。

使用泛型,您可以定義一個充當佔位符的泛型型別。我們使用泛型型別的示例:

class Stack<T> {
   private var items: [T] = []
   func push(_ item: T) {
      items.append(item)
   }
   func pop() -> T? {
      guard let lastItem = items.last else {
         return nil
      }
      items.removeLast()
      return lastItem
   }
}

泛型使用菱形運算子定義,在本例中,我們稱之為 T。在初始化時,我們定義引數,然後編譯器將所有 T 替換為此型別:

let stack = Stack<Int>()
stack.push(89)
if let lastItem = stack.pop() {
   print("last item: \(lastItem)")
}

最大的優點是我們可以現在使用任何型別的堆疊。

Swift 泛型函式

在 Swift 中,我們可以建立一個可以與任何型別的資料一起使用的函式。這樣的函式稱為泛型函式。

示例

以下是如何在 Swift 中建立泛型函式:

// creating a generic function
func displayData<T>(data: T) {
   print("Example of generic function: ")
   print("Received Data: ", data)
}

// generic function working with String
displayData(data: "Swift")

// generic function working with Int
displayData(data: 5)

輸出

Example of generic function:
Received Data: Swift
Example of generic function:
Received Data: 5

在上面的示例中,我們建立了一個名為 displayData() 的泛型函式,其型別引數為 <T>。

泛型類

與泛型函式可以與任何型別的資料一起使用的方式相同,我們還可以建立一個可以與任何型別的資料一起使用的類。泛型地講,這樣的類稱為泛型類。

讓我們看一個例子

class Stack<T> {
   private var items: [T] = []
   func push(_ item: T) {
      items.append(item)
   }
   func pop() -> T? {
      guard let lastItem = items.last else {
         return nil
      }
      items.removeLast()
      return lastItem
   }
}

在上面的示例中,我們建立了一個名為 Stack 的泛型類。此類可用於處理任何型別的資料。

Swift 泛型中的型別約束

但是,泛型有一個缺點,因為它們可以是任何型別。即使泛型相同,兩個泛型的比較也不會起作用。

class Stack<T> {
   private var items: [T] = []
   func push(_ item: T) {
      items.append(item)
   }
   func pop() -> T? {
      guard let lastItem = items.last else {
         return nil
      }
      items.removeLast()
      return lastItem
   }
   func isItemInStack(item: T) -> Bool {
      var found = false
      for stackItem in items {
         if stackItem == item { // Compiling Error
            found = true
         }
      }
      return found
   }
}

函式 isItemInStack(item:T) 中存在編譯器錯誤,因為只有當它們對應的型別支援 Equatable 協議時,才能比較兩個值。但是,可以約束泛型的型別。在這種情況下,泛型的第一行必須實現 Equatable 協議:

class Stack<T: Equatable> {
   // rest of the code will be the same as in the above example
}

結論

使用泛型,您可以透過建立可重用的程式碼來避免程式碼重複。如果您不習慣編寫泛型程式碼,您可以不必使用它。但是,泛型允許您建立可持續的程式碼併為將來需要重用相同程式碼的情況做好準備。

感謝泛型,我們不僅可以使用值作為引數,還可以使用型別。此外,Swift 編譯器可以由於其自動型別推斷而將泛型與型別匹配,從而檢查程式碼的正確性。

泛型應僅在必要時使用。在需要之前使程式碼泛型是過早最佳化,這會使您的程式碼不必要地複雜。最好從特定程式碼開始,只有在遇到具體情況時才將其泛化。

更新於:2022年12月9日

瀏覽量:181

開啟您的職業生涯

完成課程獲得認證

開始學習
廣告
© . All rights reserved.