泛型代码能够根据自定义的需求,编写出适用于任意类型、灵活可重用的函数及类型。
泛型能够避免代码的重复,用一种清晰和抽象的方式来表达代码的意图。
泛型是 Swift 最强大的特性之一,许多 Swift 标准库是通过泛型代码构建的。
Swift 的 Array和 Dictionary都是泛型集合。你可以创建一个 Int数组,也可创建一个 String数组,甚至可以是任意其他 Swift 类型的数组。同样的,你也可以创建存储任意指定类型的字典。
泛型函数(Generic Functions)
- 泛型函数可以适用于任何类型。
func swapTwoValues<T>(inout a: T, inout _ b: T) {
let temporaryA = a
a = b
b = temporaryA
}
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)
- 类型参数指定并命名一个占位类型,并且紧随在函数名后面,使用一对尖括号括起来(例如 <T>)。
当然也可以用<U>
一旦一个类型参数被指定,可以用它来定义一个函数的参数类型,或者作为函数的返回类型,还可以用作函数主体中的注释类型。
类型参数会在函数调用时被实际类型所替换。
提供多个类型参数,将它们都写在尖括号中,用逗号分开。
命名类型参数(Naming Type Parameters)
在大多数情况下,类型参数具有一个描述性名字,例如 Dictionary<Key, Value>中的 Key和 Value,以及Array<Element>中的 Element,这可以告诉阅读代码的人这些类型参数和泛型函数之间的关系。然而,当它们之间没有有意义的关系时,通常使用单个字母来命名,例如 T、U、V。
使用大写字母开头的驼峰命名法(例如 T和 MyTypeParameter)来为类型参数命名,以表明它们是占位类型,而不是一个值。
泛型类型(Generic Types)
- 除了泛型函数,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 个字符串
扩展一个泛型类型(Extending a Generic Type)
- 当扩展一个泛型类型的时候,并不需要在扩展的定义中提供类型参数列表。原始类型定义中声明的类型参数列表在扩展中可以直接使用,并且这些来自原始类型中的参数名称会被用作原始定义中类型参数的引用。
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.”
类型约束(Type Constraints)
类型约束可以指定一个类型参数必须继承自指定类,或者符合一个特定的协议或协议组合。
所有的 Swift 基本类型(例如 String、Int、Double 和 Bool)默认都是可哈希的。
当创建自定义泛型类型时,可以定义类型约束,这些约束将提供更为强大的泛型编程能力。抽象概念,例如可哈希的,描述的是类型在概念上的特征,而不是它们的显式类型。
可以在一个类型参数名后面放置一个类名或者协议名,并用冒号进行分隔,来定义类型约束,它们将成为类型参数列表的一部分。
func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
// 这里是泛型函数的函数体部分
}
- 所有的 Swift 标准类型自动支持Equatable协议。
func findIndex<T: Equatable>(array: [T], _ valueToFind: T) -> Int? {
for (index, value) in array.enumerate() {
if value == valueToFind {
return index
}
}
return nil
}
let doubleIndex = findIndex([3.14159, 0.1, 0.25], 9.3)
// doubleIndex 类型为 Int?,其值为 nil,因为 9.3 不在数组中
let stringIndex = findIndex(["Mike", "Malcolm", "Andrea"], "Andrea")
// stringIndex 类型为 Int?,其值为 2
关联类型(Associated Types)
关联类型为协议中的某个类型提供了一个占位名(或者说别名),其代表的实际类型在协议被遵循时才会被指定。
可以通过 associatedtype关键字来指定关联类型。
利用扩展可以让一个已存在的类型符合一个协议,这包括使用了关联类型的协议。这时,关联类型就被确定了。
关联类型主要是将泛型运用于协议,在实现协议的时候再确定关联类型所代表的类型
protocol Container {
associatedtype ItemType
mutating func append(item: ItemType)
var count: Int { get }
subscript(i: Int) -> ItemType { get }
}
struct IntStack: Container {
// IntStack 的原始实现部分
var items = [Int]()
mutating func push(item: Int) {
items.append(item)
}
mutating func pop() -> Int {
return items.removeLast()
}
// Container 协议的实现部分
// typealias ItemType = Int // 指定关联类型;这里可以推断出来,这句不写也可以
mutating func append(item: Int) {
self.push(item)
}
var count: Int {
return items.count
}
subscript(i: Int) -> Int {
return items[i]
}
}
extension Stack: Container {
// Container 协议的实现部分
// typealias ItemType = Element // 指定关联类型;这里可以推断出来,这句不写也可以
mutating func append(item: Element) {
self.push(item)
}
var count: Int {
return items.count
}
subscript(i: Int) -> Element {
return items[i]
}
}
Where 子句()
可以为泛型函数或泛型类型的类型参数定义一些强制要求。
可以在参数列表中通过 where子句为关联类型定义约束。
func allItemsMatch<
C1: Container, C2: Container
where C1.ItemType == C2.ItemType, C1.ItemType: Equatable>
(someContainer: C1, _ anotherContainer: C2) -> Bool {
// 检查两个容器含有相同数量的元素
if someContainer.count != anotherContainer.count {
return false
}
// 检查每一对元素是否相等
for i in 0..<someContainer.count {
if someContainer[i] != anotherContainer[i] {
return false
}
}
// 所有元素都匹配,返回 true
return true
}
var stackStrings = Stack<String>()
stackStrings.push("uno")
stackStrings.push("dos")
stackStrings.push("tres")
extension Array: Container {}
var arrayOfStrings = ["uno", "dos", "tres"]
if allItemsMatch(stackStrings, arrayOfStrings) {
print("All items match.")
} else {
print("Not all items match.")
}
// 打印 “All items match.”