解释泛型以及如何在 Swift 中应用方法或变量泛型?
在本教程中,您将学习如何使用泛型使您的代码在应用程序中可重用且灵活。您将通过示例了解如何创建可与任何类型的函数和属性。
什么是泛型?
Swift 允许您使用泛型编写灵活、可重用的代码。泛型代码可以与任何类型一起使用。您可以编写避免重复并以清晰、抽象的方式表达其意图的代码。
泛型是 Swift 最强大的功能之一,Swift 标准库的大部分内容都是用泛型代码构建的。您在整个语言指南中一直在使用泛型,即使您没有意识到这一点。
例如,Swift 的 Array 和 Dictionary 类型都是泛型集合。您可以创建一个包含 Int 值的数组或一个包含 String 值的数组。您可以构造几乎任何可以在 Swift 中构建的类型数组。同样,您可以创建一个字典来存储任何指定类型的值,并且对该类型没有任何限制。
为什么我们需要使用泛型?
一个很好的泛型用例示例是堆栈。堆栈是一个具有两个操作的容器:"推送"用于将项目添加到容器中,"拉取"用于从容器中移除最后一个项目。作为第一步,我们在不使用泛型的情况下对堆栈进行编程。结果如下所示 −
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 中创建泛型函数的方法 -
// 创建泛型函数 func displayData<T>(data: T) { print("通用函数示例: ") print("接收数据: ", data) } // 通用函数与 String 配合使用 displayData(data: "Swift") // 通用函数与 Int 配合使用 displayData(data: 5)
输出
通用函数示例: 接收数据:Swift 通用函数示例: 接收数据: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> { // 其余代码将与上例相同 }
结论
使用泛型,您可以通过创建可重用的代码来避免代码重复。如果您不习惯编写泛型代码,那么您应该可以远离它。但是,泛型允许您创建可持续的代码并为将来需要重用相同代码的情况做好准备。
多亏了泛型,我们不仅可以将值用作参数,还可以将类型用作参数。此外,Swift 编译器可以通过自动类型推断将泛型与类型匹配,从而检查代码的正确性。
泛型只应在必要时使用。在需要之前就将代码变为泛型是过早优化,这会使代码变得不必要地复杂。最好从特定的代码开始,只有当遇到具体情况时才将其泛化。