泛型

泛型代码让你能够自定义要求,编写出适用于任意类型、灵活可重用的函数及类型。它能够让你避免代码重复,用一种清晰抽象的方式来表达意图。

泛型函数

1
2
3
4
5
func swapTwoValues<T>(_ a: inout T, _ b: inout T){
let temporaryA = a
a = b
b = temporaryA
}

这个尖括号告诉Swift那个T是swapTwoValues函数定义的一个占位类型名,因此Swift不会去查找名为T的实际类型

类型参数

你可以提供多个类型参数,将他们都写在尖括号中,用逗号隔开

命名类型参数

大多数情况下,命名参数具有一个描述性名字,例如 Dictionary<Key, Value> 中的Key和Value,以及Array中的Element

泛型类型

除了泛型函数,Swift还允许你定义泛型类型.

1
2
3
4
5
6
7
8
9
struct Stack<Element> {
var items = [Element]()
mutating func push(_ item: Element){
items.append(item)
}
mutating func pop() -> Element {
return items.removeLast()
}
}

Element 为待提供的类型定义了一个占位名。

1
2
3
4
5
6
var stackOfString = Stack<String>()
stackOfString.push("uno")
stackOfString.push("dos")
stackOfString.push("tres")

let fromThePop = stackOfStirng.pop()
扩展一个泛型类型
1
2
3
4
5
6
7
8
9
10
11
extension Stack {
var topItems: Element? {
return items.isEmpty ? nil : items[items.count -1]
}
}
//这个扩展并没有定义一个类型参数列表。相反的,Stack类型已有的类型参数名称Element,
被用在扩展中表示计算型topItems的可选类型。

if let topItem = stackOfString.topItem {
print("The top item on the stack is \(topItem)")
}

类型约束

类型约束是指定一个类型参数必须继承指定类,或者符合一个特定的协议或协议组合。例如,Swift中的Dictionary类型对字典的键类型做了限制。字典的键必须是可哈希(hashable)的。

类型约束语法
1
2
3
func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U){

}
类型约束实践
1
2
3
4
5
6
7
8
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标准库中定义了一个Equatalbe协议。该协议要求任何遵循的类型必须实现等式符(==)及不等符(!=),从而能对改类型的任意两个值进行比较

1
2
3
4
5
6
7
func findIndex<T: Equatable>(of valueToFind: T, in array: [T]) -> Int ? {
for (index, value) in array.enumerated() {
if value == valueToFind {
return index
}
}
}

关联类型

关联类型为协议中的某个类型提供一个占位名,其代表的实际类型在协议被采纳时才会被指定。你可以通过 associatedtype关键字来指定关联类型。

实践
1
2
3
4
5
6
7
//定义一个Container 协议,该协议定义了一个关联类型Item
protocol Container {
associatedtype Item
mutating func append(_ item: Item)
var count: Int { get }
subscript(i: Int) -> Item { get }
}

定义一个非泛型的IntStack类型。采纳并符合了Container协议

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
struct IntStack: Container {
//原始实现
var items = [Int]()
mutating func push(_ item: Int){
items.append(item)
}
mutating func pop() -> Int {
return items.removeLast()
}
//Container 协议实现部分
typealias Item = Int
mutating func append(_ item: Int){
self.push(item)
}
var count: Int {
return items.count
}
subscript(i: Int) -> Int {
return items[i]
}
}

指定Item为Int类型,即typealias Item = Int,从而将Container协议中抽象的Item类型转换为具体的Int类型

下面定义泛型Stack结构体并且遵循Container协议

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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类型已经提供了Container协议所有的属于、方法、以及下标的实现。这意味着你只需要简单的申明Array采纳协议就可以扩展Array

1
extension Array: Container {}

定义了这个扩展之后,你可以将任意的Array当做Container

给关联类型添加约束
1
2
3
4
5
6
protocol Container {
associatedtype Item: Equatable
mutating func append(_ item: Item)
var count: Int { get }
subscript(i: Int)-> Item { get }
}

为了遵循Container协议,Item类型也必须遵循Equatable协议

在关联类型约束里使用协议

协议可以作为自身的要求出现

1
2
3
4
protocol SuffixableContainer : Container {
associatedtype Suffix: SuffixableContainer where Suffix.Item == Item
func suffix(_ size: Int) -> Suffix
}

这个协议中的Suffix是个关联类型。它有2个约束。它必须遵循SuffixableContainer协议(就是当前定义的协议),以及它的Item类型必须和容器里的Item类型相同

1
2
3
4
5
6
7
8
9
extension Stack: SuffixableContainer {
func suffix(_ size: Int) -> Stack {
var result = Stack<Int>()
for index in (count-size)..<count {
result.append(self[index])
}
return result
}
}

泛型Where语句

类型约束让你能够为泛型函数,下标,类型的类型参数定义一些强制要求。where子句跟一个或多个针对关联类型的约束,以及一个或多个类型参数和关联类型间的相等关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
}

函数参数列表的两个类型的要求

  1. C1必须符合 Container 协议
  2. C2必须符合 Container 协议
  3. C1的Item和C2的Item类型相同
  4. C1的Item必须符合Equatable协议
具有泛型Where子句的扩展
1
2
3
4
5
6
7
8
extension Stack where Element: Equatalbe {
func isTop(_ item: Element) -> Bool {
guard let topItem = items.last else{
return false
}
return topItem == item
}
}

//如果尝试在其元素不符合Equatable协议栈上调用isTop(_:)方法,则会编译报错

使用where字句去扩展一个协议

where子句要求Item符合协议

1
2
3
4
5
6
7
8
9
10
11
extension Container where Item: Equatable {
func startsWith(_ item: Item) -> Bool {
return count > 1 && self[0] == item
}
}

if [8, 9, 9].statsWith(43){
print("Start Wiht 43")
}else {
print("Start with something else")
}

where字句要求Item为特定的类型

1
2
3
4
5
6
7
8
9
10
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([125.0, 23.00, 323.00].average())
具有泛型Where字句的关联类型

你可以在关联类型后面加上具有泛型where的子句

1
2
3
4
5
6
7
8
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
}

迭代器(Iterator)的泛型where字句要求,无论迭代器是什么类型,迭代器中的元素必须和容器项目的类型保持一致

泛型下标

下标是能够泛型的,他们能够包含泛型where子句

1
2
3
4
5
6
7
8
9
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
}
}
0%