- Swift提供了泛型,可以写出灵活且可重用的函数和类型
- Swift标准库是通过了泛型代码构建出来的
- Swift的数组和字典类型都是泛型集
例子
1、创建一个非泛型函数exchange用来交换两个Int值
1 | // inout将一个值类型参数以引用方式传递 |
同理,交换两个String值和Double值的函数,对应如下:
1 | func swapTwoStrings(_ a:inout String, _ b:inout String) { |
从上面的代码中可以看出,功能是一样的,只是交换的类型不一样。那么就可以使用泛型,从而避免重复的代码。
泛型使用了占位类型名(T表示)来代替实际类型名(如:Int, String, Double…)
1 | func swapTwoValues<T>(_ a:inout T, _ b:inout T) { |
泛型类型
Swift允许自定义泛型类型。
自定义类、结构体和枚举可以使用与任何类型,类似于Array和Dictionary。
下面编写一个名为Stack(栈)的泛型集合类型
。
了解一下栈的定义:栈是一系列值得有序集合,和Array类似,但它比Array类型有更多的操作限制。数组Array允许在任意位置插入新的元素或是删除其中任意位置的元素,而栈只允许在集合的末端添加新的元素(称之为
入栈
)。类似的,栈只能从末端移除元素(称之为出栈
)
下图展示栈的入栈(push)和出栈(pop)的行为:
从左往右看,每一列对应下列数字:
- 栈中有三个值
- 有新的值被压入到栈的顶部
- 栈中有四个值,最新的值在顶部
- 栈中最顶部的值被移除出栈
- 移除后,栈中又变为三个值
总结:栈中的值遵循
先进后出
或后进先出
的原则
以Int类型的栈为例,写一个非泛型的栈:
1 | struct IntStack { |
IntStack结构体只能用于Int类型,为了能够处理任意类型的值,需要定义一个泛型的Stack结构体:
1 | struct Stack<Element> { |
扩展泛型类型
扩展泛型类型时,不需要在扩展的定义中提供类型参数列表。因为在原始类型中声明的类型参数列表在扩展中可以直接使用,并且这些来自原始类型中的参数名称会被用作原始定义中类型参数的引用。
1 | struct Stack<Element> { |
类型约束
定义泛型类型可以用作任何类型,不过,有时候需要在使用在泛型函数和泛型类型中的类型添加一个特定的类型约束。类型约束可以指定一个类型参数必须继承自指定类,或者符合一个特定的协议或协议组合。
类型约束语法
在一个类型参数名后面放置一个类型或者协议名,并用冒号进行分隔来定义类型约束,它们将成为类型参数列表的一部分。
1 | func someFunction<T: SomeClass, U:SomeProtocol>(someT:T, someU:U) { |
类型约束实践
定义一个非泛型在字符串数组中寻找字符串下标的方法
1 | func findIndex(ofString valueToFind:String, in array:[String]) -> Int? { |
定义一个泛型的:
1 | func findIndex<T>(ofValue valueToFind:T, in array:[T]) -> Int? { |
这个函数无法通过编译:Binary operator ‘==’ cannot be applied to two ‘T’ operands
。因为不是所有的Swift类型都可以用等式符(==)进行比较。那怎么办呢?
Swift标准库中定义了一个Equatable协议,该协议要求任何遵循该协议的类型都必须实现等式符(==)及不等式符(!=),从而能对该类型的任意两个值进行比较。所有的Swift标准类型自动支持Equatable协议。
1 | func findIndex<T: Equatable>(ofValue valueToFind:T, in array:[T]) -> Int? { |
关联类型
关联类型为协议中的某个类型提供了一个占位名(或者说别名),其代表的实际类型在协议被采纳时才会被指定,通过associatedtype关键字来指定关联类型。
什么意思呢?通俗地讲就是:声明协议的时候,提供一个占位名,至于这个是什么类型,看遵循该协议时指定什么类型,那么这个占位名就是什么类型
1 | // 定义Container协议 |
Container协议定义了三个任何采纳了该协议的类型(即容器)必须提供的功能:
- 必须可以通过append(_:)方法添加一个新的元素到容器里。
- 必须可以通过count属性获取容器中元素的数量,并返回一个Int值
- 必须可以通过索引值类型为Int的下标检索到容器中的每一个元素
这个协议没有指定容器中元素该如何存储,以及元素必须是何种类型。协议只指定了三个任何遵循Container协议的类型必须提供的功能。遵循协议的类型满足这三个条件的情况下也可以提供其他额外的功能。
任何遵循Container协议的类型必须能够指定其存储的元素的类型,必须保证只有正确类型的元素可以添加到容器中,必须明确通过其下标返回的元素的类型。
非泛型遵循Container协议
1 | // 定义Container协议 |
因为IntStack符合Container协议,Swift秩序通过append(_:)方法的item参数类型和下标返回值的类型,就能推断出ItemType的具体类型,所以typealias ItemType = Int
删除,运行也可以。
泛型遵循Container协议:
1 | struct Stack<Element>: Container { |
约束关联类型
可以给协议的关联类型添加类型注释,让遵循协议的类型必须遵循这个约束条件
1 | // 定义Container协议 |
泛型where语句
类型约束能够确保类型符合泛型函数或类的定义约束,可以在参数列表中通过where语句定义参数的约束,可以写一个where语句,紧跟在类型参数列表后面,where语句后跟一个或多个针对关联类型的约束,以及一个或多个类型和关联类型间的等价关系。
1 | // 定义Container协议 |
参考: