泛型代码让你能根据自定义的需求,编写出适用于任意类型的、灵活可复用的函数及类型。你可避免编写重复的代码,而是用一种清晰抽象的方式来表达代码的意图。
泛型函数
泛型函数可适用于任意类型
func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
let temporaryA = a
a = b
b = temporaryA
}
var someInt = 3
var anotherInt = 107
swapTwoValues(&someInt, &anotherInt)
// someInt 现在是 107, 而 anotherInt 现在是 3
var someString = "hello"
var anotherString = "world"
swapTwoValues(&someString, &anotherString)
// someString 现在是 "world", 而 anotherString 现在是 "hello"
类型参数
- 类型参数指定并命名一个占位类型,并且紧随在函数名后面,使用一对尖括号括起来(例如 <T>)。
- 你可以通过在尖括号内写多个类型参数名称,并用逗号分隔,来提供多个类型参数。
- 大多情况下,类型参数具有描述性的名称,例如字典 Dictionary<Key, Value> 中的 Key 和 Value 及数组 Array<Element> 中的 Element,这能告诉阅读代码的人这些类型参数与泛型类型或函数之间的关系。
- 它们之间没有有意义的关系时,通常使用单个字符来表示,例如 T、U、V
- 请使用大写字母开头的驼峰命名法来为类型参数命名,例如 T 和 MyTypeParameter,以表明它们是占位类型,而不是一个值。
泛型类型
除了泛型函数,Swift 还允许自定义泛型类型。这些自定义类、结构体和枚举可以适用于任意类型,类似于 Array 和 Dictionary。
- 每当你需要一个严格的”后进先出”方式来管理集合,栈都是最实用的模型。
struct Stack<Element> {
var items: [Element] = []
mutating func push(_ item: Element) {
items.append(item)
}
mutating func pop() -> Element {
return items.removeLast()
}
}
var stackOfStrings = Stack<String>()
stackOfStrings.push("uno")
stackOfStrings.push("dos")
stackOfStrings.push("tres")
stackOfStrings.push("cuatro")
// 栈中现在有 4 个字符串
let fromTheTop = stackOfStrings.pop()
// fromTheTop 的值为 "cuatro",现在栈中还有 3 个字符串
泛型扩展
- 当对泛型类型进行扩展时,你并不需要提供类型参数列表作为定义的一部分。
- 可以在扩展中直接使用原始类型定义中的类型参数列表,并且这些来自原始类型中的参数名称会被用作原始定义中类型参数的引用。
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).")
}
// 打印 "The top item on the stack is tres."
类型约束
- 类型约束指定类型参数必须继承自特定的类,或者遵循特定协议或协议组合。
- Swift 的 Dictionary 类型对字典的键的类型做了些限制。字典键的类型必须是可哈希的(hashable)。
- 你可以在创建自定义泛型类型时定义自己的类型约束,这些约束为泛型编程提供了强大的功能。像 Hashable 这样的抽象概念根据类型的概念特征而不是其具体类型来描述类型。
- 你可以通过在类型参数的名称后添加一个类或协议约束,并用冒号分隔,来编写类型约束。
func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
// function body goes here
}
- Swift 标准库中定义了一个 Equatable 协议,该协议要求任何遵循该协议的类型必须实现等式符(==)及不等符(!=),从而能对该类型的任意两个值进行比较。
- 所有的 Swift 标准类型自动支持 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
关联类型
- 定义一个协议时,声明一个或多个关联类型作为协议定义的一部分将会非常有用。
- 关联类型为协议中的某个类型提供了一个占位符名称,其代表的实际类型在协议被遵循时才会被指定。
- 关联类型通过 associatedtype 关键字来指定。
protocol Container {
associatedtype Item
mutating func append(_ item: Item)
var count: Int { get }
subscript(i: Int) -> Item { get }
}
struct Stack<Element>: Container {
// Stack<Element> 的原始实现部分
var items: [Element] = []
mutating func push(_ item: Element) {
items.append(item)
}
mutating func pop() -> Element {
return items.removeLast()
}
// Container 协议的实现部分
mutating func append(_ item: Element) {
self.push(item)
}
var count: Int {
return items.count
}
subscript(i: Int) -> Element {
return items[i]
}
}
扩展现有类型来指定关联类型
- Swift 的 Array 类型已经提供 append(_:) 方法,count 属性,以及带有 Int 索引的下标来检索其元素。这三个功能都遵循 Container 协议的要求,也就意味着你只需声明 Array 遵循 Container 协议,就可以扩展 Array,使其遵循 Container 协议。
- Array 已有的 append(_:) 方法和下标使 Swift 能够推断出 Item 的具体类型,就像上面提到的泛型 Stack 类型一样。在定义此扩展后,你可以将任何 Array 作为 Container 使用。
extension Array: Container {}
给关联类型添加约束
你可以在协议中为关联类型添加类型约束,以要求遵循该协议的类型满足这些约束。
// 为了遵守 Container 协议,Item 类型也必须遵守 Equatable 协议。
protocol Container {
associatedtype Item: Equatable
mutating func append(_ item: Item)
var count: Int { get }
subscript(i: Int) -> Item { get }
}
在关联类型约束里使用协议
协议可以作为它自身的要求出现。
protocol SuffixableContainer: Container {
associatedtype Suffix: SuffixableContainer where Suffix.Item == Item
func suffix(_ size: Int) -> Suffix
}
extension Stack: SuffixableContainer {
func suffix(_ size: Int) -> Stack {
var result = Stack()
for index in (count-size)..<count {
result.append(self[index])
}
return result
}
// 推断 suffix 结果是Stack。
}
var stackOfInts = Stack<Int>()
stackOfInts.append(10)
stackOfInts.append(20)
stackOfInts.append(30)
let suffix = stackOfInts.suffix(2)
// suffix 包含 20 和 30
泛型Where语句
- 对关联类型添加约束通常是非常有用的。你可以通过定义一个泛型 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 {
// 检查两个容器含有相同数量的元素
if someContainer.count != anotherContainer.count {
return false
}
// 检查每一对元素是否相等
for i in 0..<someContainer.count {
if someContainer[i] != anotherContainer[i] {
return false
}
}
// 所有元素都匹配,返回 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.")
}
// 打印 "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
}
}
if stackOfStrings.isTop("tres") {
print("Top element is tres.")
} else {
print("Top element is something else.")
}
// 打印 "Top element is tres."
struct NotEquatable { }
var notEquatableStack = Stack<NotEquatable>()
let notEquatableValue = NotEquatable()
notEquatableStack.push(notEquatableValue)
notEquatableStack.isTop(notEquatableValue) // 报错
- 你可以使用泛型 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 子句在上面的例子中要求 Item 遵循一个协议,但你也可以编写一个泛型 where 子句去要求 Item 为特定类型。
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())
// 打印 "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())
// 打印 "648.75"
print(numbers.endsWith(37))
// 打印 "true"
- 如果你想在不使用上下文 where 子句的情况下编写这段代码,你需要为每个泛型 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 子句。
- 你可以在 subscript 之后的尖括号内写入占位符类型,并在下标主体的开括号({)之前写入泛型 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
}
}