Swift中泛型的力量。 第一部分

大家好! 我们正在与您分享专门为“ iOS Developer”课程的学生准备的翻译 高级课程 好好阅读。



泛型函数,泛型类型和类型限制

什么是仿制药?


当他们工作时,您爱他们,而当他们不工作时,您恨他们!

在现实生活中,每个人都知道仿制药的功效:早上起床,决定喝什么,加满杯子。

Swift是一种类型安全的语言。 每当使用类型时,都需要显式指定它们。 例如,我们需要一个可以使用多种类型的函数。 Swift具有类型AnyAnyObject ,但应谨慎使用而不是始终使用它们。 使用AnyAnyObject将使您的代码不可靠,因为在编译过程中无法跟踪类型不匹配。 这是仿制药来的地方。

通用代码允许您创建可重用的函数和数据类型,这些函数和数据类型可与满足某些限制的任何类型一起使用,同时确保编译期间的类型安全。 这种方法允许您编写有助于避免重复的代码,并以清晰的抽象方式表达其功能。 例如,诸如ArraySetDictionary类的类型使用泛型来存储元素。

假设我们需要创建一个由整数值和字符串组成的数组。 为了解决这个问题,我将创建两个函数。

 let intArray = [1, 2, 3, 4] let stringArray = [a, b, c, d] func printInts(array: [Int]) { print(intArray.map { $0 }) } func printStrings(array: [String]) { print(stringArray.map { $0 }) } 

现在,我需要输出一个float类型的元素数组或一个用户对象数组。 如果我们看一下上面的函数,我们将看到仅使用类型上的差异。 因此,我们可以编写通用函数以供重用,而不必复制代码。

Swift中泛型的历史




通用功能


泛型函数可以与类型T任何通用参数一起使用T 类型名称并没有说明应该是什么,但是它说两个数组都必须是类型,而不管是什么。 每次调用print( _: )函数时,都会确定要使用的类型本身而不是

 func print<T>(array: [T]) { print(array.map { $0 }) } 

通用类型或参数多态


上例中的通用类型T是类型参数。 您可以通过在尖括号中用逗号分隔多个类型参数名称来指定多个类型参数。

如果查看Array和Dictionary <Key,Element>,您会注意到它们具有命名的类型参数,即Element和Key,Element,它表示类型参数与使用该类型参数的泛型类型或函数之间的关系。 。

注意:始终以CamelCase表示法为类型参数提供名称(例如TTypeParameter ),以表明它们是类型的名称,而不是值。

通用类型


这些是可以与任何类型一起使用的自定义类,结构和枚举,类似于数组和字典。

让我们创建一个堆栈

 import Foundation enum StackError: Error { case Empty(message: String) } public struct Stack { var array: [Int] = [] init(capacity: Int) { array.reserveCapacity(capacity) } public mutating func push(element: Int) { array.append(element) } public mutating func pop() -> Int? { return array.popLast() } public func peek() throws -> Int { guard !isEmpty(), let lastElement = array.last else { throw StackError.Empty(message: "Array is empty") } return lastElement } func isEmpty() -> Bool { return array.isEmpty } } extension Stack: CustomStringConvertible { public var description: String { let elements = array.map{ "\($0)" }.joined(separator: "\n") return elements } } var stack = Stack(capacity: 10) stack.push(element: 1) stack.push(element: 2) print(stack) stack.pop() stack.pop() stack.push(element: 5) stack.push(element: 3) stack.push(element: 4) print(stack) 

现在,该堆栈只能接受整数元素,并且如果我需要存储其他类型的元素,则需要创建另一个堆栈,或者将其转换为通用外观。

 enum StackError: Error { case Empty(message: String) } public struct Stack<T> { var array: [T] = [] init(capacity: Int) { array.reserveCapacity(capacity) } public mutating func push(element: T) { array.append(element) } public mutating func pop() -> T? { return array.popLast() } public func peek() throws -> T { guard !isEmpty(), let lastElement = array.last else { throw StackError.Empty(message: "Array is empty") } return lastElement } func isEmpty() -> Bool { return array.isEmpty } } extension Stack: CustomStringConvertible { public var description: String { let elements = array.map{ "\($0)" }.joined(separator: "\n") return elements } } var stack = Stack<Int>(capacity: 10) stack.push(element: 1) stack.push(element: 2) print(stack) var strigStack = Stack<String>(capacity: 10) strigStack.push(element: "aaina") print(strigStack) 

通用类型限制


由于泛型可以是任何类型,因此您将无法做太多事情。 有时将约束应用于可以与泛型函数或泛型类型一起使用的类型,这很有用。 类型限制表明类型参数必须匹配特定的协议或协议组成。

例如,“快速Dictionary类型对可用作字典键的类型施加了限制。 字典要求对键进行哈希处理,以便能够检查它是否已经包含特定键的值。

 func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) { // function body goes here } 

本质上,我们创建了一个T类型的堆栈,但是我们不能比较两个堆栈,因为这里的类型与Equatable不匹配。 我们需要更改它以使用Stack< T: Equatable >

泛型如何工作? 让我们来看一个例子。

 func min<T: Comparable>(_ x: T, _ y: T) -> T { return y < x ? y : x } 

编译器缺少创建功能代码所需的两件事:

  • 类型T的变量的大小;
  • 函数<的特定重载的地址,必须在运行时调用。

每当编译器遇到通用类型的值时,它就会将该值放在容器中。 此容器具有用于存储值的固定大小。 如果值太大,Swift会在堆上分配它,并将链接存储在容器中。

编译器还为每个通用参数维护一个或多个见证表的列表:一个用于值的见证表,以及一个用于每个类型限制协议的见证表。 见证表用于在运行时将函数调用动态发送到所需的实现。

第一部分结束。 按照传统,我们正在等待您的评论,朋友。

第二部分

Source: https://habr.com/ru/post/zh-CN462753/


All Articles