本文基于Swift5.0版本官方文档,阅读大概需要20min,可以对泛型有一个清晰的认识。
什么是泛型(Generics)
泛型:指的就是在你定义的时候放置一个占位符类型名,告诉系统使用的类型现在不确定,我先占个位置。这样编译的时候系统不会报错。然后在你使用的时候才会真正地确定类型。
语言表达起来可能不是那么直观,让我们看一段代码直观的了解一下什么是泛型。
通过上面的代码我们可以看出,通过在函数名字后面添加<T>
来表明添加了一个泛型类型,<>
告诉编译器T
是一个占位符类型,不需要真正的查找叫做T
的类型。
Note
<T>
中的T可以是任意字符或者单词,但是要使用大写字母或者大写开头的驼峰命名法(如:V
、U
、MyTypeParameter
)。<>
里面不止可以写一个类型占位符,也可以写多个:<T, U>
。
现在我们初步了解泛型是什么,那么肯定会有人问道:我们为什么要是使用泛型呢?下面我们看一下为什么要使用泛型。
为什么要用泛型
泛型类型函数
在日常工作中,我们会经常遇到在某些条件下交换两个变量的值的情况。如果需要交换两个Int
的值得话,我们可以很轻易的写下下面的函数:
这个函数很简洁,也很正确,但是如果我们还需要交换String
、Double
等等类型的变量呢,再写swapTwoStringValue(_:_:)
、swapTwoDoubleValue(_:_:)
的函数吗?再定义两个这样的函数当然没有问题,但是我们会发现这三个函数内部实现都是一样的,区别只是参数的类型不同。这时候就轮到泛型出马了,我们可以用泛型写一个使用任意Swift基本类型的交换函数:
小结-为什么要用泛型
- 可以写出更加灵活可复用的函数。
- 使代码更加简洁明了。
Note
- 上面的
swapTwoValue(_:_:)
函数只是举个例子说明泛型类型函数的用法,如果你想使用交换两个变量的值得功能,你可以使用官方的swap(_:_:)
函数。 - 注意交换函数两个变量的类型都是
T
,虽然T
可以表示任意类型,但两个变量必须是同一类型,Swift不允许两个不同类型的变量交换值,因为Swift是一门类型安全的语言。
我们现在知道可以通过定义泛型类型的函数来达到减少代码冗余的问题,那么泛型的用处仅仅如此吗?作为Swift最强大的特性之一,肯定不会只是实现一个泛型类型函数这么简单的。下面让我们看一下泛型还能做什么?
我们能用它做什么
实现泛型的数据结构
我们可以通过泛型来实现一个支持多种类型的栈。具体代码如下:
Stack可以放入Int
/String
等多种类型的数据。此处有个地方要注意:在我们给Stack扩展计算属性或者方法的时候,不需要我们在声明类型参数,Stack中的泛型在extension中依然有效
。具体代码如下:
|
|
通过类型约束(Type Constraints)来实现遵守protocol的任意类型参数的函数
日常开发中,我们会经常需要实现在一个数组中查找某个值的索引的功能,如果我们将数组的元素类型写死的话,我们声明的函数只能用于某一种类型,这时候我们应该怎么办呢?对了,就是将类型声明为泛型类型。通过上面的介绍,我们可能会写下以下代码:
这个函数创建的很不错,但是很可惜它编译会报错:Binary operator '==' cannot be applied to two 'T' operands
,该报错表示我们声明的类型占位符T
不能使用==
运算符。那么如何正确的实现该函数呢?这就要使用类型约束(Type Constraints)来实现了。具体的做法就是将findIndex<T>
改为<T: Equatable>
,这句话的意思是T
只支持实现了Equatable
协议的类型使用。具体代码如下:
Protocol中的联合类型
现在我们知道可以在函数参数中使用泛型,那么我能在protocol
中实现类似的功能吗?答案是:当然可以。我们可以用associatedtype
关键字来告诉编译器该类型为泛型,在真正使用的时候再检查它的类型。
假如我们要实现一个Container
的Protocol,该协议包含了append(_)
添加元素的函数、获取长度的计算属性count
、根据下标获取元素的函数subscript(_)
。这时候如果我们将Item的类型写死的话就说明了只有这一种类型能够遵守该Protocol,那么如何让更多的类型能够遵守呢?这时候就轮到associatedtype
出场了。下面为具体代码:
|
|
我们可以使Stack
遵守该协议,看一下具体使用。代码如下:
Protocol中的联合类型添加类型约束
在上面我们看到Protocol中可以使用联合类型来实现泛型,那么我们也可以给联合类型添加类型约束来实现泛型遵守某个Protocol、或者遵守某种条件(比如类型相同等)。具体代码如下:
Protocol中的联合类型添加多个类型约束
我们知道可以给联合类型添加类型约束,可以用associatedtype Item: Equatable
来使Item遵守Equatable协议,那如果我想让Item遵守Equatable的同时,又约束它必须是某一种类型呢?这时候我们可以使用where
语句来实现。具体代码如下:
上面的代码SuffixableContainer
实现了一个获取某个位置到最后的一段数据。
Extension中使用类型约束
同Protocol,我们也可以在Extension中通过where
来实现类型约束。如果我们不让Element遵守Equatable协议的话,是会编译错误的,因为在该函数中我们使用了 == 操作符
。代码如下:
当然,我们也可以在扩展Protocol的时候来使用类型约束。代码如下:
除了强制泛型元素遵守某个协议外,我们也可以强制泛型元素为特定的某个类型。代码如下:
总结
上面就是关于泛型的讲解,下面来看一下关于泛型的总结。
- 类型占位符要使用大写的字母或者大写开头的驼峰命名法。
- 泛型使代码更加灵活可复用、更加简洁明了。
类型参数
或有类型约束的参数
可以在泛型函数、泛型下标、泛型类型中使用。- 泛型的
where
语句可以使你的联合类型必须遵守某个协议或者满足某些条件。
到这里,关于泛型的讲解就结束了。希望大家通过本文能对泛型有一个全新的、深刻的认识。让我们在项目中愉快的使用泛型吧!