通用代码使您可以编写灵活,可重用的函数和类型,这些函数和类型可根据您定义的要求与任何类型一起使用。您可以编写避免重复的代码,并以清晰抽象的方式表达其意图。
泛型是Swift最强大的功能之一,许多Swift标准库都是使用泛型代码构建的。实际上,即使您没有意识到,您在整个《语言指南》中都使用了泛型。例如,Swift的Array和Dictionary类型都是通用集合。您可以创建一个包含Int值的数组,或者一个包含String值的数组,或者实际上是可以在Swift中创建的任何其他类型的数组。同样,您可以创建一个字典来存储任何指定类型的值,并且对该类型可以没有限制。
泛型解决的问题
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
let temporaryA = a
a = b
b = temporaryA
}
如In-Out参数中所述,此函数利用in-out参数交换a和b的值
swapTwoInts(: :)函数将b的原始值交换为a,然后将a的原始值交换为b。您可以调用此函数来交换两个Int变量中的值:
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"
swapTwoInts(: :)函数很有用,但只能与Int值一起使用。如果要交换两个String值或两个Double值,则必须编写更多函数,例如下面显示的swapTwoStrings(: :)和swapTwoDoubles(: :)函数
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
}
Generic Functions
您可能已经注意到swapTwoInts(: :),swapTwoStrings(: :)和swapTwoDoubles(: :)函数的主体是相同的。唯一的区别是它们接受的值的类型(Int,String和Double)。
编写一个交换任何类型的两个值的函数会更有用,而且也更加灵活。通用代码使您可以编写此类功能。 (下面定义了这些功能的通用版本。)
func swapTwoValues(_ a: inout T, _ b: inout T) {
let temporaryA = a
a = b
b = temporaryA
}
swapTwoValues(: :)函数的主体与swapTwoInts(: :)函数的主体相同。但是,swapTwoValues(: :)的第一行与swapTwoInts(: :)略有不同。以下是第一行的比较:
func swapTwoInts(_ a: inout Int, _ b: inout Int)
func swapTwoValues(_ a: inout T, _ b: inout T)
泛型函数可以使用任何类型。这是上面的swapTwoInts(: :)函数的通用版本,称为swapTwoValues(: :):
该函数的通用版本使用占位符类型名称(在这种情况下称为T),而不是实际的类型名称(例如Int,String或Double)。占位符类型名称并没有说明T必须是什么,而是表示无论T代表什么,a和b都必须具有相同的T类型。每次调用swapTwoValues(: :)函数时,都会确定要代替T使用的实际类型。
泛型函数和非泛型函数之间的另一个区别是,泛型函数的名称(swapTwoValues(: :))后跟尖括号()中的占位符类型名称(T)。方括号告诉Swift,T是swapTwoValues(: :)函数定义中的占位符类型名称。由于T是一个占位符,因此Swift不会查找称为T的实际类型。
swapTwoValues(: :)函数现在可以用与swapTwoInts相同的方式调用,除了可以传递任何类型的两个值外,只要这两个值彼此具有相同的类型即可。每次调用swapTwoValues(: :)时,都会从传递给函数的值的类型中推断出用于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"
Type Parameters
在上面的swapTwoValues(: :)示例中,占位符类型T是类型参数的示例。类型参数指定并命名一个占位符类型,并在函数名称后立即写在一对匹配的尖括号(例如
一旦指定了类型参数,就可以使用它来定义函数参数的类型(例如swapTwoValues(: :)函数的a和b参数),或者作为函数的返回类型,或者作为类型功能体内的注释。在每种情况下,每当调用函数时,type参数都将替换为实际类型。 (在上面的swapTwoValues(: :)示例中,第一次调用该函数时,将T替换为Int,第二次调用该函数时,将其替换为String。)
通过在尖括号内用逗号分隔多个类型参数名称,可以提供多个类型参数。
Naming Type Parameters
在大多数情况下,类型参数具有描述性名称,例如Dictionary 中的Key和Value和Array 中的Element,它向读者介绍类型参数与其所使用的泛型类型或函数之间的关系。但是,当它们之间没有有意义的关系时,通常使用单个字母(例如T,U和V)来命名它们,例如上面的swapTwoValues(: :)函数中的T。
请始终为类型参数提供驼峰式的大写名称(例如T和MyTypeParameter),以表明它们是类型的占位符,而不是值。
Generic Types
除了通用函数,Swift还使您能够定义自己的通用类型。这些是可以与任何类型一起使用的自定义类,结构和枚举,类似于数组和字典的方式。
本节说明如何编写称为Stack的通用集合类型。堆栈是一组有序的值,类似于数组,但是操作集比Swift的Array类型更受限制。数组允许在数组中的任何位置插入和删除新项目。但是,堆栈允许将新项目仅附加到集合的末尾(称为将新值压入堆栈)。同样,堆栈仅允许从集合的末尾删除项目(称为从堆栈弹出值)
UINavigationController类使用堆栈的概念在其导航层次结构中对视图控制器进行建模。您调用UINavigationController类的pushViewController(:animated :)方法将视图控制器添加(或推送)到导航堆栈上,并调用其popViewControllerAnimated( :)方法从导航堆栈中删除(或弹出)视图控制器。每当您需要严格的“后进先出”方法来管理集合时,堆栈都是有用的集合模型
以下是编写非通用版本的堆栈的方法,在这种情况下,是针对Int值的堆栈:
struct IntStack {
var items = [Int]()
mutating func push(_ item: Int) {
items.append(item)
}
mutating func pop() -> Int {
return items.removeLast()
}
}
这是相同代码的通用版本
truct Stack {
var items = [Element]()
mutating func push(_ item: Element) {
items.append(item)
}
mutating func pop() -> Element {
return items.removeLast()
}
}
Extending a Generic Type
扩展通用类型时,不会在扩展定义中提供类型参数列表。而是在扩展程序的正文中提供原始类型定义的类型参数列表,并且原始类型参数名称用于引用原始定义的类型参数。
下面的示例扩展了通用Stack类型,以添加一个名为topItem的只读计算属性,该属性返回堆栈上的顶层项目,而不会将其从堆栈中弹出:
extension Stack {
var topItem: Element? {
return items.isEmpty ? nil : items[items.count - 1]
}
}
Type Constraints
swapTwoValues(: :)函数和Stack类型可以与任何类型一起使用。但是,有时对可与泛型函数和泛型类型一起使用的类型实施某些类型约束很有用。类型约束指定类型参数必须从特定的类继承,或符合特定的协议或协议组成。
例如,Swift的Dictionary类型对可用作字典键的类型进行了限制。如字典中所述,字典键的类型必须是可哈希的。也就是说,它必须提供一种使其自身具有唯一代表性的方法。字典需要其键可哈希,以便它可以检查它是否已经包含特定键的值。没有此要求,Dictionary无法确定是否应为特定键插入或替换一个值,也无法为词典中已经存在的给定键找到值。
通过对Dictionary的密钥类型进行类型约束来强制执行此要求,该约束指定密钥类型必须符合Hashable协议,该协议是Swift标准库中定义的特殊协议。默认情况下,所有Swift的基本类型(例如String,Int,Double和Bool)都是可哈希的。
Type Constraint Syntax
您可以在创建自定义泛型类型时定义自己的类型约束,这些约束提供了泛型编程的许多功能。像Hashable这样的抽象概念根据类型的概念特征而不是具体类型来表征类型。
func someFunction(someT: T, someU: U) {
// function body goes here
}
上面的假设函数有两个类型参数。第一个类型参数T具有类型约束,该约束要求T成为SomeClass的子类。第二个类型参数U具有类型约束,该约束要求U遵守协议SomeProtocol。
func findIndex(of valueToFind: T, in array:[T]) -> Int? {
for (index, value) in array.enumerated() {
if value == valueToFind {
return index
}
}
return nil
}
参考代码
https://docs.swift.org/swift-book/LanguageGuide/Generics.html
更多SwiftUI教程和代码关注专栏
- 请关注我的专栏icloudend, SwiftUI教程与源码
https://www.jianshu.com/c/7b3e3b671970