泛型详解以及如何在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

开启您的职业生涯

完成课程获得认证

开始学习
广告