Swift-泛型

  • Swift提供了泛型,可以写出灵活且可重用的函数和类型
  • Swift标准库是通过了泛型代码构建出来的
  • Swift的数组和字典类型都是泛型集

例子

1、创建一个非泛型函数exchange用来交换两个Int值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// inout将一个值类型参数以引用方式传递
func swapTwoInts(_ a:inout Int, _ b:inout Int) {
let temp = a
a = b
b = temp
}

var number1 = 33
var number2 = 55
print("交换前数据:number1:\(number1), number2:\(number2)")
// 交换前数据:number1:33, number2:55
swapTwoInts(&number1, &number2)
print("交换后数据:number1:\(number1), number2:\(number2)")
// 交换后数据:number1:55, number2:33

同理,交换两个String值和Double值的函数,对应如下:

1
2
3
4
5
6
7
8
9
10
11
func swapTwoStrings(_ a:inout String, _ b:inout String) {
let temp = a
a = b
b = temp
}

func swapTwoDoubles(_ a:inout Double, _ b:inout Double) {
let temp = a
a = b
b = temp
}

从上面的代码中可以看出,功能是一样的,只是交换的类型不一样。那么就可以使用泛型,从而避免重复的代码。
泛型使用了占位类型名(T表示)来代替实际类型名(如:Int, String, Double…)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
func swapTwoValues<T>(_ a:inout T, _ b:inout T) {
let temp = a
a = b
b = temp
}

var number1 = 33
var number2 = 55
print("交换前数据:number1:\(number1), number2:\(number2)")
// 交换前数据:number1:33, number2:55
swapTwoValues(&number1, &number2)
print("交换后数据:number1:\(number1), number2:\(number2)")
// 交换后数据:number1:55, number2:33


var string1 = "hello"
var string2 = "world"
print("交换前数据:string1:\(string1), number2:\(string2)")
// 交换前数据:string1:hello, number2:world
swapTwoValues(&string1, &string2)
print("交换后数据:string1:\(string1), number2:\(string2)")
// 交换后数据:string1:world, number2:hello

泛型类型

Swift允许自定义泛型类型。

自定义类、结构体和枚举可以使用与任何类型,类似于Array和Dictionary。

下面编写一个名为Stack(栈)的泛型集合类型

了解一下栈的定义:栈是一系列值得有序集合,和Array类似,但它比Array类型有更多的操作限制。数组Array允许在任意位置插入新的元素或是删除其中任意位置的元素,而栈只允许在集合的末端添加新的元素(称之为入栈)。类似的,栈只能从末端移除元素(称之为出栈

下图展示栈的入栈(push)和出栈(pop)的行为:

image

从左往右看,每一列对应下列数字:

  1. 栈中有三个值
  2. 有新的值被压入到栈的顶部
  3. 栈中有四个值,最新的值在顶部
  4. 栈中最顶部的值被移除出栈
  5. 移除后,栈中又变为三个值

总结:栈中的值遵循先进后出后进先出的原则

以Int类型的栈为例,写一个非泛型的栈:

1
2
3
4
5
6
7
8
9
10
11
12
struct IntStack {
var items = [Int]()
// 标记为mutating,因为需要修改结构体的items数组
// 向栈中压入值
mutating func push(_ item: Int){
items.append(item)
}
// 向栈中移除值
mutating func pop() ->Int{
return items.removeLast()
}
}

IntStack结构体只能用于Int类型,为了能够处理任意类型的值,需要定义一个泛型的Stack结构体:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
struct Stack<Element> {
var items = [Element]()
// 标记为mutating,因为需要修改结构体的items数组
// 向栈中压入值
mutating func push(_ item: Element){
items.append(item)
}
// 向栈中移除值
mutating func pop() ->Element{
return items.removeLast()
}
}

// 创建string类型的结构体
var stackString = Stack<String>()
print("字符串元素开始入栈:")
stackString.push("google")
stackString.push("safari")
print(stackString.items)
//字符串元素开始入栈:
//["google", "safari"]

let deleteItem = stackString.pop()
print("出栈元素:\(deleteItem)")
// 出栈元素:safari

// =================================

// 创建Int类型的结构体
var stackInt = Stack<Int>()
print("Int元素开始入栈:")
stackInt.push(4)
stackInt.push(66)
print(stackInt.items)
//Int元素开始入栈:
//[4, 66]

扩展泛型类型

扩展泛型类型时,不需要在扩展的定义中提供类型参数列表。因为在原始类型中声明的类型参数列表在扩展中可以直接使用,并且这些来自原始类型中的参数名称会被用作原始定义中类型参数的引用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
struct Stack<Element> {
var items = [Element]()
// 标记为mutating,因为需要修改结构体的items数组
// 向栈中压入值
mutating func push(_ item: Element){
items.append(item)
}
// 向栈中移除值
mutating func pop() ->Element{
return items.removeLast()
}
}

extension Stack {
// 扩展声明topItem可选属性,类型是Element泛型
var topItem:Element? {
// 判断数组是否为空,是返回nil,不是返回最后一个元素
return items.isEmpty ? nil :items[items.count - 1]
}
}

var stackString = Stack<String>()
stackString.push("google")
stackString.push("safari")

if let topItem = stackString.topItem {
print("最后一个元素是:\(topItem)")
// 最后一个元素是:safari
}

类型约束

定义泛型类型可以用作任何类型,不过,有时候需要在使用在泛型函数和泛型类型中的类型添加一个特定的类型约束。类型约束可以指定一个类型参数必须继承自指定类,或者符合一个特定的协议或协议组合。

类型约束语法

在一个类型参数名后面放置一个类型或者协议名,并用冒号进行分隔来定义类型约束,它们将成为类型参数列表的一部分。

1
2
3
func someFunction<T: SomeClass, U:SomeProtocol>(someT:T, someU:U) {
// 这里是泛型函数的函数体部分
}
类型约束实践

定义一个非泛型在字符串数组中寻找字符串下标的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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 fondIndex = findIndex(ofString: "llama", in: strings) {
print("The index of llama is \(fondIndex)")
// The index of llama is 2
}

定义一个泛型的:

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

这个函数无法通过编译:Binary operator ‘==’ cannot be applied to two ‘T’ operands
。因为不是所有的Swift类型都可以用等式符(==)进行比较。那怎么办呢?

Swift标准库中定义了一个Equatable协议,该协议要求任何遵循该协议的类型都必须实现等式符(==)及不等式符(!=),从而能对该类型的任意两个值进行比较。所有的Swift标准类型自动支持Equatable协议。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func findIndex<T: Equatable>(ofValue valueToFind:T, in array:[T]) -> Int? {
for (index, value) in array.enumerated() {
if value == valueToFind {
return index
}
}
return nil
}
let strings = ["cat", "dog", "llama", "parakeet", "terrapin"]
if let fondIndex = findIndex(ofValue: "llama", in: strings) {
print("The index of llama is \(fondIndex)")
// The index of llama is 2
}

let ints = [2, 5, 8, 9, 10]
if let fondIndex = findIndex(ofValue: 9, in: ints) {
print("The index of 9 is \(fondIndex)")
// The index of 9 is 3
}

关联类型

关联类型为协议中的某个类型提供了一个占位名(或者说别名),其代表的实际类型在协议被采纳时才会被指定,通过associatedtype关键字来指定关联类型。

什么意思呢?通俗地讲就是:声明协议的时候,提供一个占位名,至于这个是什么类型,看遵循该协议时指定什么类型,那么这个占位名就是什么类型

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

Container协议定义了三个任何采纳了该协议的类型(即容器)必须提供的功能:

  • 必须可以通过append(_:)方法添加一个新的元素到容器里。
  • 必须可以通过count属性获取容器中元素的数量,并返回一个Int值
  • 必须可以通过索引值类型为Int的下标检索到容器中的每一个元素

这个协议没有指定容器中元素该如何存储,以及元素必须是何种类型。协议只指定了三个任何遵循Container协议的类型必须提供的功能。遵循协议的类型满足这三个条件的情况下也可以提供其他额外的功能。

任何遵循Container协议的类型必须能够指定其存储的元素的类型,必须保证只有正确类型的元素可以添加到容器中,必须明确通过其下标返回的元素的类型。

非泛型遵循Container协议

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
// 定义Container协议
protocol Container {
// 定义了一个关联类型ItemType
associatedtype ItemType
mutating func append(_ item: ItemType)
var count: Int { get }
subscript(i:Int) ->ItemType? { get }
}


struct IntStack: Container {

var items = [Int]()
// 标记为mutating,因为需要修改结构体的items数组
// 向栈中压入值
mutating func push(_ item: Int){
items.append(item)
}
// 向栈中移除值
mutating func pop() ->Int{
return items.removeLast()
}

// Container实现协议部分
// Container中ItemType指定为Int
typealias ItemType = Int

mutating func append(_ item: Int) {
self.push(item)
}

var count: Int {
return items.count
}

subscript(i: Int) -> Int? {
if i >= items.count {
return nil
}
return items[i]
}
}

var intStack = IntStack()
intStack.push(22)
intStack.push(5)
print("容器数目:\(intStack.count)") // 容器数目:2
intStack.append(99)
intStack.append(6)
print("容器数目:\(intStack.count)") // 容器数目:4
if let indexOfValue = intStack[2] {
print("容器下标为2的元数是:\(indexOfValue)")
// 容器下标为2的元数是:99
}

因为IntStack符合Container协议,Swift秩序通过append(_:)方法的item参数类型和下标返回值的类型,就能推断出ItemType的具体类型,所以typealias ItemType = Int删除,运行也可以。

泛型遵循Container协议:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
struct Stack<Element>: Container {

var items = [Element]()
// 标记为mutating,因为需要修改结构体的items数组
// 向栈中压入值
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? {
if i >= items.count {
return nil
}
return items[i]
}

}

var stack = Stack<Int>()
stack.push(22)
stack.push(5)
print("容器数目:\(stack.count)") // 容器数目:2
stack.append(99)
stack.append(6)
print("容器数目:\(stack.count)") // 容器数目:4
if let indexOfValue = stack[2] {
print("容器下标为2的元数是:\(indexOfValue)")
// 容器下标为2的元数是:99
}

var stackString = Stack<String>()
stackString.push("google")
stackString.push("safari")
print("容器数目:\(stackString.count)") // 容器数目:2
stackString.append("firefox")
stackString.append("ie")
print("容器数目:\(stackString.count)") // 容器数目:4
if let indexOfValue = stackString[2] {
print("容器下标为2的元数是:\(indexOfValue)")
// 容器下标为2的元数是:firefox
}
约束关联类型

可以给协议的关联类型添加类型注释,让遵循协议的类型必须遵循这个约束条件

1
2
3
4
5
6
7
8
// 定义Container协议
protocol Container {
// 定义了一个关联类型ItemType,且必须遵循Equatable协议
associatedtype ItemType: Equatable
mutating func append(_ item: ItemType)
var count: Int { get }
subscript(i:Int) ->ItemType? { get }
}

泛型where语句

类型约束能够确保类型符合泛型函数或类的定义约束,可以在参数列表中通过where语句定义参数的约束,可以写一个where语句,紧跟在类型参数列表后面,where语句后跟一个或多个针对关联类型的约束,以及一个或多个类型和关联类型间的等价关系。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
// 定义Container协议
protocol Container {
// 定义了一个关联类型ItemType
associatedtype ItemType
mutating func append(_ item: ItemType)
var count: Int { get }
subscript(i:Int) ->ItemType { get }
}

struct Stack<Element>: Container {

var items = [Element]()
// 标记为mutating,因为需要修改结构体的items数组
// 向栈中压入值
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]
}
}

// 扩展,将Array当做Container来使用
extension Array :Container {}

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
}

var arr1 = Stack<String>()
arr1.push("google")
arr1.push("safair")
arr1.push("firefox")

let arr2 = ["google", "safair", "firefox"]

if allItemsMatch(arr1, arr2) {
print("匹配所有元素")
} else {
print("元素匹配")
}
// 匹配所有元素

参考: