泛型函数
泛型函数可以适用于任何类型:
func swapTwoValues<T>(_ a: inout T, _ b: inout T) { let temporaryA = a
a= b
b = temporaryA
}
func swapTwoInts(_ a: inout Int, _ b: inout Int)
func swapTwoValues<T>(_ a: inout T, _ b: inout T)
这个函数的泛型版本使用了占位类型名来代替实际类型名。占位类型名没有指明T必须是什么类型,但是它指明了a和b必须是同一类型T,无论T代表什么类型,只有swapTwoValues(_:_:)函数在调用时,才能根据传入的实际类型决定T所代表的类型。
另外一个不同之处在于这个泛型函数名后面跟着占位类型名,并用尖括号括起来。这个尖括号告诉swift那个T是swapTwoValues(_:_:)函数定义内的一个占位类型名,因此swift不会去查找名为T的实际类型。
var someInt = 3
var anotherInt = 107 swapTwoValues(&someInt, &anotherInt)
// someInt 现在 107, and anotherInt 现在 3
var someString = "hello"
var anotherString = "world"
swapTwoValues(&someString, &anotherString)
// someString 现在 "world", and anotherString 现在 "hello"
泛型类型
除了泛型函数,swift还允许你定义泛型。这些自定义类、结构体和枚举可以适用于任何类型,类似于Array和Dicitionary。
这部分内容将向你展示如果编写一个名为Stack的泛型集合类型。栈是一系列值的有序集合,和Array类似,但它相比swift的Array类型有更多的操作限制。数组允许在数组的任意位置插入元素或是删除其中任意位置的元素。而栈只允许在集合的末端添加新的元素。
struct Stack<Element> {
var items = [Element]()
mutating func push(item: Element) {
items.append(item)
}
mutating func pop() -> Element {
return items.removeLast()
} }
这些方法被标记为mutating,因为它们需要修改结构体的Item数组。
扩展一个泛型类型
当你扩展一个泛型类型的时候,并不需要再扩展的定义中提供类型参数列表。原始类型定义汇总声明的类型参数列表在扩展中可以直接使用,并且这些来自原始类型中的参数名称会被用作原始定义中类型参数的引用。 extension Stack {
var topItem: Element? {
return items.isEmpty ? nil : items[items.count - 1]
}
}
计算型属性现在可以用来访问任意Stack实例的顶端元素且不移除它:
if let topItem = stackOfStrings.topItem {
print("The top item on the stack is \(topItem).")
}
类型约束
下面的函数为非泛型函数,该函数的功能是在一个String数组中查找给定String值的索引。
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)")
}
下面展示的上述的函数的泛型版本
func findIndex<T>(of valueToFind: T, in array:[T]) -> Int? {
for (index, value) in array.enumerated() {
if value == valueToFind {
return index
} }
return nil }
上面所写的函数无法通过编译。问题出在相等性检查上,不是所有的swift类型都可以用等式符进行比较。不过,所有的这些并不会让我们无从下手。swift标准库中定义了一个Equatable协议,该协议要求任何遵循该协议的类型必须实现等式符及不等符,从而能对该类型的任何两个值进行比较。所有的swift标准类型自动支持Equatable协议。
func findIndex<T: Equatable>(array: [T], _ valueToFind: T) -> Int? {
for (index, value) in array.enumerate() {
if value == valueToFind {
return index
} }
return nil }
任何符合Equatable协议的类型T都能用这个泛型函数:
let doubleIndex = findIndex(of: 9.3, in: [3.14159, 0.1, 0.25])
// doubleIndex 类型为 Int?,其值为 nil,因为 9.3 不在数组中
let stringIndex = findIndex(of: "Andrea", in: ["Mike", "Malcolm", "Andrea"]) // stringIndex 类型为 Int?,其值为 2
关联类型
下面例子定义了一个Container协议,该协议定义了一个关联类型:
protocol Container {
associatedtype ItemType
mutating func append(item: ItemType)
var count: Int { get }
subscript(i: Int) -> ItemType { get }
}
由于swift的类型推断,实际上不用再Stack的定义中声明ItemType为Int。因为Stack符合Container协议的所有要求,swift只需通过append(_:)方法的item参数类型和下标返回值的类型,就可以推断出ItemType的具体类型。实际上,如果在不写typealias ItemType = Int 这一行,一切仍旧可以正常工作,因为swift清楚的知道ItemType应该是那种类型:
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]
}
}
泛型Where语句
为关联类型定义约束也是非常有用的。可以在参数列表中通过where子句为关联类型定义约束。能通过where子句要求一个关联类型遵从某个特定的协议,以及某个特定的类型参数和关联类型必须类型相同。可以通过将where关键字跟在类型参数列表后面来定义where子句,where子句后跟一个或者多个针对关联类型的约束,以及一个或多个类型参数和关联类型间的相等关系。可以在函数体或者类型的大括号之前添加where子句。func allItemsMatch<C1: Container, C2: Container>
(_ someContainer: C1, _ anotherContainer: C2) -> Bool
where C1.ItemType == C2.ItemType, C1.ItemType: Equatable {
// 检查两个容器含有相同数量的元素
if someContainer.count != anotherContainer.count {
return false
}
// 检查每一对元素是否相等
for i in 0..<someContainer.count {
if someContainer[i] != anotherContainer[i] {
return false
} }
// 所有元素都匹配,返回 true return true
}
这个函数接受两个参数,参数someContainer的类型为C1,参数anotherContainer的类型为C2。C1和C2是容器的两个占位类型参数,函数被调用时才能确定他们的具体类型。
这个函数的类型参数列表还定义了对两个类型参数的要求:
C1必须符合Container协议
C2必须符合Container协议
C1的ItemType必须和C2的ItemType类型相同
C1的ItemType必须符合Equatable协议
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.”
取自《the swift programming language》中文版