引入
泛型代码能够编写灵活、 可重复使用的函数和类型, 这些函数和类型可根据您定义的要求与任何类型协同工作。 你可以编写避免重复的代码, 并以清晰、抽象的方式表达其意图。
泛型是 Swift 最强大的功能之一, Swift 标准库的大部分内容都是使用泛型代码构建的。
泛型解决的问题
下面是非泛型函数。
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
let temporaryA = a
a = b
b = temporaryA
}
var someInt = 3
var anotherInt = 107
swapTwoInts(&someInt, &anotherInt)
print("someInt is now \(someInt), and anotherInt is now \(anotherInt)")
// Prints "someInt is now 107, and anotherInt is now 3"
这个函数只能交换Int值, 如果要交换字符串或者浮点数就需要在写下面的函数。
func swapTwoStrings(_ a: inout String, _ b: inout String) {
let temporaryA = a
a = b
b = temporaryA
}
func swapTwoDoubles(_ a: inout Double, _ b: inout Double) {
let temporaryA = a
a = b
b = temporaryA
}
这些函数主体是相同的, 唯一的不同是操作的值的类型。 泛型可以编写更加灵活的通用函数。
泛型函数
泛型函数可以处理任何类型。
func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
let temporaryA = a
a = b
b = temporaryA
}
泛型函数使用了占位符名称(这里是T), 而不是具体的类型名。 占位符名称没有说明具体类型, 但是说明了a和b必须是相同的T类型, 无论T代表什么。 每次调用泛型函数时, 就会实际确定T的类型。
泛型函数与非泛型函数的另一个区别是: 泛型函数名称右边有一对尖括号,里面是占位符名称。 Swift 由此知道T是占位符, 不会查找T的实际类型。
现在可以传入任意两个类型的值, 只要类型相同, 就可以交换。
var someInt = 3
var anotherInt = 107
swapTwoValues(&someInt, &anotherInt)
// someInt is now 107, and anotherInt is now 3
var someString = "hello"
var anotherString = "world"
swapTwoValues(&someString, &anotherString)
// someString is now "world", and anotherString is now "hello"
类型参数
上面的占位符类型T就是类型参数的一个例子。 类型参数指定并命名一个占位符类型, 紧跟在函数名后, 写在一对尖括号中。
指定类型参数后, 可以用它定义参数类型, 或者函数的返回类型, 或者在函数主体中做类型注解。 只要函数被调用, 类型参数才会被实际类型替换。
可以在尖括号内写入多个类型参数名, 中间使用逗号分隔。
命名类型参数
大多数情况下, 类型参数都有描述性名称。 例如:
- Dictionary
<Key, Value>
- Array
<Element>
中的 Element
这些名称会告诉读者类型参数与通用类型或函数之间的关系。 然而, 当它们之间没有有意义的关系时, 传统的做法是使用 T、U 和 V 等单字母来命名它们。
泛型类型
除了泛型函数, Swift还允许定义自己的泛型类型。 这些是自定义类、 结构和枚举, 可以与任何类型一起使用, 与 Array 和 Dictionary 的使用方式类似。
下面是非泛型的Stack写法
struct IntStack {
var items: [Int] = []
mutating func push(_ item: Int) {
items.append(item)
}
mutating func pop() -> Int {
return items.removeLast()
}
}
下面是泛型版本
struct Stack<Element> {
var items: [Element] = []
mutating func push(_ item: Element) {
items.append(item)
}
mutating func pop() -> Element {
return items.removeLast()
}
}
泛型版本增加了Element类型参数, 写在结构名称后面的尖括号中, 在结构主体中就可以使用 Element替代固定类型了。
现在可以使用泛型结构创建任意类型的Stack, 需要在尖括号中写明类型。
var stackOfStrings = Stack<String>()
stackOfStrings.push("uno")
stackOfStrings.push("dos")
stackOfStrings.push("tres")
stackOfStrings.push("cuatro")
// the stack now contains 4 strings
let fromTheTop = stackOfStrings.pop()
// fromTheTop is equal to "cuatro", and the stack now contains 3 strings
扩展泛型
扩展泛型时, 不在扩展定义中提供类型参数列表。 相反, 原始类型定义中的类型参数列表可以在扩 展的正文中使用, 原始类型参数名称用于引用原始定义中的类型参数。
extension Stack {
var topItem: Element? {
return items.isEmpty ? nil : items[items.count - 1]
}
}
if let topItem = stackOfStrings.topItem {
print("The top item on the stack is \(topItem).")
}
// Prints "The top item on the stack is tres."
扩展没有定义类型参数列表。 Stack现有的参数名称Element可以直接使用。
类型限制
泛型函数和类型可用于任何类型。 不过有时需要对类型执行某些约束, 类型约束可以规定类型参数 必需继承自特定的类, 或符合特定的协议。
类型约束语法
编写类型约束时, 可在类型参数名称后放置单个类或协议约束, 用冒号分隔, 作为类型参数列表 的一部分。
func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
// function body goes here
}
示例函数中, 参数T有类型约束, 要求是SomeClass的子类; 类型U的约束是符合SomeProtocol协议。
类型约束操作
下面的函数查找字符串在数组的索引。
func findIndex(ofString valueToFind: String, in array: [String]) -> Int? {
for (index, value) in array.enumerated() {
if value == valueToFind {
return index
}
}
return nil
}
使用方法
let strings = ["cat", "dog", "llama", "parakeet", "terrapin"]
if let foundIndex = findIndex(ofString: "llama", in: strings) {
print("The index of llama is \(foundIndex)")
}
// Prints "The index of llama is 2"
编写为泛型函数
func findIndex<T>(of valueToFind: T, in array:[T]) -> Int? {
for (index, value) in array.enumerated() {
if value == valueToFind {
return index
}
}
return nil
}
上面的代码会出现编译问题。 因为if会对两个值就行相等比较, 不是所有类型都支持等于运算。 所以我们需要修改泛型函数, 让类型T满足Equatable协议。
func findIndex<T: Equatable>(of valueToFind: T, in array:[T]) -> Int? {
for (index, value) in array.enumerated() {
if value == valueToFind {
return index
}
}
return nil
}
let doubleIndex = findIndex(of: 9.3, in: [3.14159, 0.1, 0.25])
// doubleIndex is an optional Int with no value, because 9.3 isn't in the array
let stringIndex = findIndex(of: "Andrea", in: ["Mike", "Malcolm", "Andrea"])
// stringIndex is an optional Int containing a value of 2
关联类型
在定义协议时, 有时需要声明一个或多个关联类型作为协议定义的一部分。 关联类型为协议中使用 的类型提供了一个占位符名称。 在协议被采用之前, 不会指定实际使用的关联类型。 关联类型用关 联类型关键字指定。
关联类型应用
protocol Container {
associatedtype Item
mutating func append(_ item: Item)
var count: Int { get }
subscript(i: Int) -> Item { get }
}
Container协议定义了三种必要功能:
- 必须能够通过append方法添加新项目
- 必须能够返回Int值的count属性
- 必须可以通过包含Int值的下标检索每个项目
为了定义这些要求, Container协议需要一种方法引用容器将容纳的元素的类型, 而不需要知道
具体类型是什么。 为了实现这个目标, 协议声明了Item关联类型, 写作 associatedtype Item
。
协议没有定义Item是什么, 这留给任何符合要求的类型来提供。
下面的IntStack符合容器协议。
struct IntStack: Container {
// original IntStack implementation
var items: [Int] = []
mutating func push(_ item: Int) {
items.append(item)
}
mutating func pop() -> Int {
return items.removeLast()
}
// conformance to the Container protocol
typealias Item = Int
mutating func append(_ item: Int) {
self.push(item)
}
var count: Int {
return items.count
}
subscript(i: Int) -> Int {
return items[i]
}
}
还可以编写泛型Stack支持容器协议。
struct Stack<Element>: Container {
// original Stack<Element> implementation
var items: [Element] = []
mutating func push(_ item: Element) {
items.append(item)
}
mutating func pop() -> Element {
return items.removeLast()
}
// conformance to the Container protocol
mutating func append(_ item: Element) {
self.push(item)
}
var count: Int {
return items.count
}
subscript(i: Int) -> Element {
return items[i]
}
}
为关联类型添加约束
可以在协议中为关联类型添加类型约束, 以要求符合要求的类型满足这些约束。
protocol Container {
associatedtype Item: Equatable
mutating func append(_ item: Item)
var count: Int { get }
subscript(i: Int) -> Item { get }
}
泛型Where子句
类型约束可以定义与泛型函数、下标或类型相关的类型参数要求。
定义关联类型的要求也很有用。 你可以通过定义泛型 where 子句来实现这一点。 通过通用 where 子句, 可以要求关联类型必须符合特定协议, 或要求特定类型参数和关联类型必须相同。 通用 where 子句以 where 关键字开头, 然后是关联类型的约束或类型与关联类型之间的相等关系。 通用 where 子句就写在类型或函数正文开头的大括号之前。
func allItemsMatch<C1: Container, C2: Container>
(_ someContainer: C1, _ anotherContainer: C2) -> Bool
where C1.Item == C2.Item, C1.Item: Equatable {
// Check that both containers contain the same number of items.
if someContainer.count != anotherContainer.count {
return false
}
// Check each pair of items to see if they're equivalent.
for i in 0..<someContainer.count {
if someContainer[i] != anotherContainer[i] {
return false
}
}
// All items match, so return true.
return true
}
使用方式
var stackOfStrings = Stack<String>()
stackOfStrings.push("uno")
stackOfStrings.push("dos")
stackOfStrings.push("tres")
var arrayOfStrings = ["uno", "dos", "tres"]
if allItemsMatch(stackOfStrings, arrayOfStrings) {
print("All items match.")
} else {
print("Not all items match.")
}
// Prints "All items match."
泛型where子句的扩展
你还可以使用泛型where子句作为扩展的一部分。
extension Stack where Element: Equatable {
func isTop(_ item: Element) -> Bool {
guard let topItem = items.last else {
return false
}
return topItem == item
}
}
这样可以添加一个新要求: 只有当Stack中的元素符合Equatable协议时, 才会添加isTop方法。
尝试在不符合equatable协议的元素上调用isTop方法, 会出现编译错误。
struct NotEquatable { }
var notEquatableStack = Stack<NotEquatable>()
let notEquatableValue = NotEquatable()
notEquatableStack.push(notEquatableValue)
notEquatableStack.isTop(notEquatableValue) // Error
也可以在扩展协议时使用泛型where子句。
extension Container where Item: Equatable {
func startsWith(_ item: Item) -> Bool {
return count >= 1 && self[0] == item
}
}
if [9, 9, 9].startsWith(42) {
print("Starts with 42.")
} else {
print("Starts with something else.")
}
// Prints "Starts with something else."
当然where子句也可以要求具体类型。
extension Container where Item == Double {
func average() -> Double {
var sum = 0.0
for index in 0..<count {
sum += self[index]
}
return sum / Double(count)
}
}
print([1260.0, 1200.0, 98.6, 37.0].average())
// Prints "648.9"
上下文where子句
在泛型类型的上下文中工作时, 可以编写泛型 where 子句作为声明的一部分, 而声明本身并没有泛型 类型约束。
extension Container {
func average() -> Double where Item == Int {
var sum = 0.0
for index in 0..<count {
sum += Double(self[index])
}
return sum / Double(count)
}
func endsWith(_ item: Item) -> Bool where Item: Equatable {
return count >= 1 && self[count-1] == item
}
}
let numbers = [1260, 1200, 98, 37]
print(numbers.average())
// Prints "648.75"
print(numbers.endsWith(37))
// Prints "true"
如果不想在上下文where子句汇总编写上面的代码, 就需要编写两个扩展。
extension Container where Item == Int {
func average() -> Double {
var sum = 0.0
for index in 0..<count {
sum += Double(self[index])
}
return sum / Double(count)
}
}
extension Container where Item: Equatable {
func endsWith(_ item: Item) -> Bool {
return count >= 1 && self[count-1] == item
}
}
泛型where子句的关联类型
可以在关联类型中加入泛型where子句。
protocol Container {
associatedtype Item
mutating func append(_ item: Item)
var count: Int { get }
subscript(i: Int) -> Item { get }
associatedtype Iterator: IteratorProtocol where Iterator.Element == Item
func makeIterator() -> Iterator
}
通过在协议声明中加入泛型where子句, 为关联类型添加约束。
protocol ComparableContainer: Container where Item: Comparable { }
泛型下标
下标可以是泛型, 也可以包含where子句。 在下标后的尖括号中写入占位符名称, 并在主体花括号 前写入泛型where子句。
extension Container {
subscript<Indices: Sequence>(indices: Indices) -> [Item]
where Indices.Iterator.Element == Int {
var result: [Item] = []
for index in indices {
result.append(self[index])
}
return result
}
}
容器协议的这一扩展增加了一个下标, 该下标接收一系列索引, 并返回一个数组, 其中包含每个给 定索引上的项目。限制如下:
- 括号中的泛型参数 Indices 必须是符合 Swift 标准库中 Sequence 协议的类型。
- 该下标的单个参数 indices 是 Indices 类型的实例。
- 通用 where 子句要求序列的迭代器必须遍历 Int 类型的元素。
这些限制意味着为 indices 参数传递的值是一个整数序列。