一、定义
什么是泛型?
网络上对泛型编程的定义是这样的:
- 泛型编程是一种算法机制为
types to-be-specified-later
(类型确定滞后)的计算机编程风格,当具体的类型作为参数传入后,该算法机制会对类型进行实例化。这个方法由ML
在 1973 年开创。可以用共有的函数和类型来表示一个类型集合从而来减少函数操作的重复。
苹果Swift
官方文档 关于泛型是这么说明的:
泛型编码能让你写出符合需求、支持任意类型,灵活、可重用的函数。你能够编写避免重复和编程风格抽象、清晰、优雅的代码。
泛型是
Swift
最强大的特性之一,许多Swift
标准库是通过泛型代码构建的。例如,Swift
的Array
和Dictionary
都是泛型集合。你可以创建一个Int
数组,也可创建一个String
数组,甚至可以是任意其他Swift
类型的数组。同样的,你也可以创建存储任意指定类型的字典。
泛型就是具体类型的占位符,这个占位符只有在调用的时候才会确定类型,泛型代码让你能够根据自定义的需求,编写出适用于任意类型、灵活可重用的函数及类型。它能让你避免代码的重复,用一种清晰和抽象的方式来表达代码的意图。
所以,如果泛型使用得当,能编写出可复用,简洁,可维护的代码。
二、初体验
那我们为什么要使用泛型呢,单纯的概念说明总是枯燥的,而且也难以理解,我们通过一个例子来初体验一下使用泛型的好处:
tips: 例子的功能实现使用Set
是最好的选择,这里只是为了举例说明泛型的使用。
这里有一个需求:剔除Int
数组中的重复元素
func removeIntSameElement(_ originArr: [Int]) -> [Int] {
return originArr.reduce([Int]()) { $0.contains($1) ? $0 : $0 + [$1] }
}
// 打印结果:[3, 2, 4, 5, 6]
print(removeIntSameElement([3, 2, 3, 4, 5, 2, 6, 3]))
tips: 这里使用的是Swift
高阶函数实现,如果不了解可以查看:
https://www.jianshu.com/p/26a011cd6055
然后,又来了一个需求,这次需要的是要求剔除String
数组中的重复的元素,那我们再写一个函数
func removeStringSameElement(_ originArr: [String]) -> [String] {
return originArr.reduce([String]()) { $0.contains($1) ? $0 : $0 + [$1] }
}
// 打印结果:["a", "b", "d", "e", "c"]
print(removeStringSameElement(["a", "b", "a", "d", "e", "d", "b", "c"]))
然后,如果是要实现其他类型的呢,比如Double
、Float
...,那我们就要写更多的函数来满足需求,这...
我们能够注意到,removeIntSameElement
和removeStringSameElement
以及我们将要写的满足其他类型的函数功能都是一样的,唯一的区别就是入参的变量类型不同。
当我们硬着头皮写完这些功能相同的函数之后,我们通常会碰到两种问题:
- 万一功能实现出了点问题,需要进行修复
- 完全确保了功能的实现没有问题,但是...,但是改需求了
那我们就要去把所有这些功能相同的函数实现都修改一遍,这个时候就会有一种很崩溃的感觉,自然就会想会不会有更灵活的方式来实现这些功能相同但入参的变量类型不一致的函数,于是泛型来了,完美解决这个问题。
func removeSameElement<T: Equatable>(_ originArr: [T]) -> [T] {
return originArr.reduce([T]()) { $0.contains($1) ? $0 : $0 + [$1] }
}
这个函数的函数名removeSameElement
后面跟着用尖括号括起来占位类型名(<T>
)。这个尖括号告诉Swift
那个T
是removeSameElement
函数定义内的一个占位类型名,因此Swift
不会去查找名为T
的实际类型。
removeSameElement
使用了(T
)来代替实际类型名(例如:Int
、String
或Double
)。占位类型名没有指明T
必须是什么类型,但是它指明了originArr
必须是T
的数组,无论T
代表什么类型。只有removeSameElement
函数在调用时,才会根据所传入的实际类型决定T
所代表的类型。
-
T: Equatable
表示占位类型名(T
)必须遵循Equatable
协议,这个后面会说,暂时忽略。
removeSameElement
函数现在可以像removeStringSameElement
那样调用,不同的是它能接受的是任意类型的数组。removeSameElement
函数被调用时,T
所代表的类型都会由传入的值的类型推断出来。
行不行,看疗效,调用试试,可以看到,调用结果与指定类型的函数得到的结果一致。
两次调用的T
分表代表Int
和String
。
// 泛型打印结果:[3, 2, 4, 5, 6]
print(removeSameElement([3, 2, 3, 4, 5, 2, 6, 3]))
// 泛型打印结果:["a", "b", "d", "e", "c"]
print(removeSameElement(["a", "b", "a", "d", "e", "d", "b", "c"]))
<T>
表示是类型参数,类型参数指定并命名一个占位类型,并且紧随在函数名后面,使用一对尖括号括起来,一旦一个类型参数被指定,你可以用它来定义一个函数的参数类型,或者作为函数的返回类型,还可以用作函数主体中的注释类型。在这些情况下,类型参数会在函数调用时被实际类型所替换。(在上面的removeSameElement
的例子中,当函数第一次被调用时,T
被Int
替换,第二次调用时,被String
替换。)类型参数不止可以定义一个,与函数的参数一样,根据具体的需求,可提供多个类型参数,将它们都写在尖括号中,用逗号分开。
三、基本使用
泛型在开发中的使用场景主要在以下几方面:
- 泛型函数
- 泛型类型
- 泛型约束
- 泛型下标
A. 泛型函数
泛型函数指的是:函数的参数或返回值类型使用泛型,而不是具体的类型
泛型函数的格式:
func 函数名<泛型1, 泛型2, …>(形参1, 形参2, ...) -> 返回类型 {
函数体
}
需求:实现Swift高阶函数reduce功能的一个函数
func myReduce<T, U>(arr: [T], initialValue: U, partialResult: (U, T) -> U) -> U {
var result = initialValue
arr.forEach {
result = partialResult(result, $0)
}
return result
}
myReduce
这个函数有两个占位类型,T和U:
-
T
作为形参arr
的数组元素占位类型和形参partialResult
闭包的第二个入参占位类型 - U不仅作为了形参
initialValue
和形参partialResult
闭包的第一个参数占位类型,也作为闭包partialResult
的返回类型和整个函数的返回类型
泛型函数真的可以支持所有的类型吗?看疗效:
let array = ["1", "2", "3", "4", "5"]
let r1 = myReduce(arr: array, initialValue: 0) { $0 + (Int($1) ?? 0) }
// 打印结果:转为Int后的求和结果:15
print("转为Int后的求和结果:\(r1)")
let nums = [1, 2, 3, 4, 5]
let r2 = myReduce(arr: nums, initialValue: []) { $1 % 2 == 0 ? $0 + ["\($1)"] : $0 }
// 打印结果:偶数转为字符串数组:["2", "4"]
print("偶数转为字符串数组:\(r2)")
这个泛型函数正常工作了:
第一个例子,
T
代表String
,U
代表Int
,代码意思是将数组中的每个String
元素转换成Int
然后累计求和,将String
和Int
分别代入myReduce
函数的T
和U
,就能明白了第二个例子,
T
代表Int
,U
代表[String]
,代码意思是将数组中每个Int
元素的偶数取出来转成一个新的String
数组,将Int
和[String]
本别代入myReduce
函数的T
和U
,分析一下,就明白了
B. 泛型类型
除了泛型函数,Swift
还允许定义泛型类型。这些自定义类、结构体和枚举可以适用于任何类型,类似于Array
和Dictionary
。
// Array 的定义
public struct Array<Element> {
// ...
}
// Dictionary 的定义
public struct Dictionary<Key, Value> where Key : Hashable {
// ...
}
-
Array
后面尖括号中的Element
就是Array
的定义的泛型类型 -
Dictionary
尖括号中的Key
、Value
就是Dictionary
定义的泛型类型
// Array 的使用
let a1: Array<String> = ["a", "b"]
let a2: Array<Int> = [1, 2]
// Dictionary 的使用
let d1: Dictionary<String, String> = ["a": "b"]
let d2: Dictionary<String, Int> = ["a": 1]
-
a1
的String
和a2
的Int
就是Array
定义的泛型Element
的具体类型 -
d1
的String
、String
和d2
的String
、Int
就是Dictionary
的泛型Key
、Value
的具体类型
不过一般写代码的时候,我们都是使用如下的方式:
let a: [String] = ["a", "b"]
let d: [String: String] = ["a": "b"]
所以对泛型的感知并不强。
泛型类型的使用和泛型函数差不多,就是在类型名后面加上<泛型1, 泛型2, …>,然后在类型里面直接使用泛型。
举个例子,实现一个简易版的迭代器:
struct CustomIterator<Element> {
var elements: [Element] = []
var num = 0
mutating func next() -> Element? {
if num == elements.count {
num = 0
return nil
}
num += 1
return elements[num - 1]
}
init(elements: [Element], num: Int = 0) {
self.elements = elements
self.num = num
}
}
CustomIterator
结构体名字后面的Element
就是泛型类型,对CustomIterator
来说,它实现的是一个简易迭代的功能,与类型无关,只要能装进Array
的类型都可以使用这个功能。
Element
为待提供的类型定义了一个占位名。这种待提供的类型可以在结构体的定义中通过Element
来引用。在这个例子中Element
在如下三个地方被用作占位符:
- 指定
init
方法的第一个参数必须是Element
的数组 - 创建
elements
属性,使用Element
类型的空数组对其进行初始化 - 指定
next
方法的返回值,指定是可选的Element
与泛型函数类似,都是在调用的时候会确定泛型的具体类型。
var cusIterStr = CustomIterator<String>(elements: ["2", "3", "6"])
while let ele = cusIterStr.next() {
print(ele)
}
/*
打印结果:
2
3
6
*/
var cusItemrDouble = CustomIterator<Double>(elements: [22.13, 45.67, 98.12])
while let ele = cusItemrDouble.next() {
print(ele)
}
/*
打印结果:
22.13
45.67
98.12
*/
第一个例子Element
代表的是String
,第二个例子Element
代表的是Double
,它们分别是在CustomIterator<String>
和CustomIterator<Double>
初始化的时候确定了泛型的具体类型。
扩展泛型类型
当你扩展一个泛型类型的时候,你并不需要在扩展的定义中提供类型参数列表。原始类型定义中声明的类型参数列表在扩展中可以直接使用,并且这些来自原始类型中的参数名称会被用作原始定义中类型参数的引用。
下面我们扩展泛型类型CustomIterator
,为其添加了一个名为firstElement
的只读计算型属性,返回一个CustomIterator
中数组的第一个元素:
extension CustomIterator {
var firstElement: Element? {
return elements.first
}
}
firstElement
会返回一个Element
类型的可选值。当elements
为空的时候,firstElement
会返回nil
;当elements
不为空的时候,firstElement
会返回elements
中的第一个元素。
注意,这个扩展并没有定义一个类型参数列表。相反的,CustomIterator
类型已有的类型参数名称Element
,被用在扩展中来表示计算型属性firstElement
的可选类型。
计算型属性firstElement
现在可以用来访问任意CustomIterator
实例的第一个元素:
var cusIter = CustomIterator<String>(elements: ["hello", "你好", "hi"])
// 打印结果:hello
print(cusIter.firstElement ?? "")
C. 泛型约束
myReduce
和CustomIterator
都可以作用任何类型。不过,有的时候如果能将使用在泛型函数和泛型类型中的类型添加一个特定的类型约束,将会是非常有用的。类型约束可以指定一个类型参数必须继承自指定类,或者符合一个特定的协议或协议组合,或者符合一些什么条件。
所以,泛型约束大致分为以下几种:
- 继承约束,泛型类型必须是某个类的子类类型
- 协议约束,泛型类型必须遵循某些协议
- 条件约束,泛型类型必须满足某种条件
接下来我们来逐个说明:
- 协议约束
协议约束,工作中高频率使用的Dictionary
的定义的泛型Key
和Value
中的Key
就是有协议约束的:
// Dictionary 的定义
public struct Dictionary<Key, Value> where Key : Hashable {
// ...
}
泛型Key
必须遵循Hashable
协议,所以字典的键的类型必须是可哈希(hashable
)的。也就是说,必须有一种方法能够唯一地表示它。Dictionary
的键之所以要是可哈希的,是为了便于检查字典是否已经包含某个特定键的值。若没有这个要求,Dictionary
将无法判断是否可以插入或者替换某个指定键的值,也不能查找到已经存储在字典中的指定键的值。
当你创建自定义泛型类型时,你可以定义你自己的类型约束,这些约束将提供更为强大的泛型编程能力。抽象概念,例如可哈希的,可比较的等等,而不是它们的显式类型。
函数的协议约束的语法:
// 函数的协议约束
func 函数名<泛型: 协议, ...>(形参: 泛型, ...) -> 返回值 {
// 函数体
}
func 函数名<泛型, ...>(形参: 泛型, ...) -> 返回值 where 泛型: 协议, ... {
// 函数体
}
// 函数的协议约束简单例子
func f1<T: Equatable, U: Hashable>(p1: T, p2: U) -> U {
return p2
}
func f2<T, U>(p1: T, p2: U) -> U where T: Equatable, U: Hashable {
return p2
}
以上两种写法都是可以的,f1
和f2
表达的意思一样,T
需要遵循Equatable
协议,U
需要遵循Hashable
协议。
到这就可以解释初体验的例子中T: Equatable
是什么意思了,这是对泛型T
的一个协议约束,必须要遵循Equatable
协议,因为函数体内调用了contains
方法,而contains
要求数组中的元素必须遵循Equatable
协议。
类或结构体的协议约束的语法:
// 类或结构体的泛型约束
class / struct 类名/结构体名: 泛型:协议, ... {
// ...
}
class / struct 类名/结构体名: 泛型, ... where 泛型:协议, ... {
// ...
}
class A1<T: Equatable, U: Hashable> {
// ...
}
class B1<T, U> where T: Equatable, U: Hashable {
// ...
}
struct S1<T: Equatable, U: Hashable> {
// ...
}
class S2<T, U> where T: Equatable, U: Hashable {
// ...
}
我们来为泛型类型中的例子CustomIterator
添加一个extension
,然后在extension
中添加一个方法,判断某个元素是否存在在CustomIterator
的elements
中:
extension CustomIterator {
func isExist(element: Element) -> Bool {
// 错误:Argument type 'Element' does not conform to expected type 'Equatable'
return elements.contains(element)
}
}
按正常思路,判断一个元素是否存在数组中,自然会想到调用contains
函数即可,但很可惜,编译器是报错的,因为这里使用的是泛型,也就是任意类型,而contains
函数要求数组内的元素必须遵循Equatable
协议,而泛型的任意类型自然不能保证都遵循了Equatable
协议。
所以正确的写法如下:
// 写法1.1
struct CustomIterator<Element: Equatable> {
// ...
}
// 写法1.2
struct CustomIterator<Element> where Element: Equatable {
// ...
}
// 写法2
extension CustomIterator where Element: Equatable {
func isExist(element: Element) -> Bool {
return elements.contains(element)
}
}
写法1.1和1.2的效果是一样的,我们可以对结构体或类中的泛型进行协议约束,但写法1和写法2是区别的:
- 写法1:要使用
CustomIterator
这个结构体中的任意功能,Element
就必须要遵循Equatable
协议 - 写法2:如果要使用
CustomIterator
扩展中的isExist
函数时,Element
才必须遵循Equatable
协议
也就是说,将协议约束加在不同的地方,约束的范围是不一样的:
- 加在类名或结构体名之后,要使用这个类或者协议,就必须遵循这个协议,而加
- 加在类或结构体的
extension
之后,只有要使用这个extension
中的函数时,才需要遵循这个协议,使用本体或其他没有加约束的extension
中的函数或属性时,并不需要遵循这个协议
约束的范围就看具体的需求了~
- 继承约束
顾名思义,就是说泛型必须是某个类的子类。
语法与协议约束一样,只是把协议换成具体的父类的类名。
就拿说方言(中国方言)来当例子,来自中国不同地方的人会说当地特有的方言,用程序来体现就是这样的:
// 中国人
class Chinese {
func speak() {
print("中国人说普通话~")
}
}
// 湖南人 继承自 中国人
class HuNan: Chinese {
override func speak() {
print("湖南人说湖南话~")
}
}
// 广东人 继承自 中国人
class GuangDong: Chinese {
override func speak() {
print("广东人说广东话~")
}
}
// 定义泛型函数(中国人说话),接受一个泛型参数,自然要求该泛型类型必须继承Chinese
func chineseSpeak<T: Chinese>(_ chinese: T) {
chinese.speak()
}
// 打印结果:湖南人说湖南话~
// 广东人说广东话~
chineseSpeak(HuNan())
chineseSpeak(GuangDong())
通过代码可以看到,继承约束与协议约束非常类似,只是泛型的限制条件一个是具体的父类,一个是协议。
- 条件约束
在说条件约束之前,我们得先说说另一个概念,那就是关联类型:
定义一个协议时,有的时候声明一个或多个关联类型作为协议定义的一部分将会非常有用。关联类型为协议中的某个类型提供了一个占位名(或者说别名),其代表的实际类型在协议被采纳时才会被指定。
这样的协议称之为泛型协议,可以通过associatedtype
关键字来指定关联类型。
下面例子定义了一个MyProtocol
协议,该协议定义了一个关联类型MyType
:
protocol MyProtocol {
associatedtype MyType
mutating func append(_ item: MyType)
}
MyProtocol
定义了一个append
方法,添加一个新元素。MyProtocol
协议需要指定任何通过append
方法添加到容器中的元素和容器中的元素是相同类型。
为了达到这个目的,MyProtocol
协议声明了一个关联类型MyType
,写作 associatedtype MyType
。这个协议无法定义MyType
是什么类型的别名,这个信息将留给遵循协议的类型来提供。尽管如此,MyType
别名提供了一种方式来引用 MyProtocol
中元素的类型,并将之用于append
方法,从而保证append
方法能够正如预期地被执行。
下面是上文的CustomIterator
简易版迭代器)遵循MyProtocol
协议:
struct CustomIterator<Element>: MyProtocol {
var elements: [Element] = []
var num = 0
mutating func next() -> Element? {
if num == elements.count {
num = 0
return nil
}
num += 1
return elements[num - 1]
}
init(elements: [Element], num: Int = 0) {
self.elements = elements
self.num = num
}
// MARK: - MyProtocol
mutating func append(_ item: Element) {
elements.append(item)
}
}
由于Swift
的类型推断,并不需要指定MyProtocol
中MyType
的具体类型,CustomIterator
只需通过append
方法的item
参数类型,就可以推断出 MyType
的具体类型。
上述代码中append
的实现,占位类型参数Element
被用作append
方法的item
参数。Swift
可以据此推断出Element
的类型即是MyType
的类型。
说完了关联类型,接下来说条件约束。
通过where
子句要求一个关联类型遵从某个特定的协议,以及某个特定的类型参数和关联类型必须类型相同。可以通过将where
关键字紧跟在函数体或者类型的大括号后面来定义where
子句,where
子句后跟一个或者多个针对关联类型的约束,以及一个或多个类型参数和关联类型间的相等关系。
下面的例子定义了一个名为isAllItemsMatch
的泛型函数,用来检查两个自定义迭代器实例是否包含相同顺序的相同元素。如果所有的元素能够匹配,那么返回true
,否则返回false
。
func isAllItemsMatch<A1: MyProtocol, A2: MyProtocol>(_ ite1: A1, _ ite2: A2) -> Bool where A1.MyType == A2.MyType, A1.MyType: Equatable {
if ite1.count != ite2.count { return false }
for i in 0..<arr1.count {
if ite1[i] != ite2[i] {
return false
}
}
return true
}
// 以下为配套代码
protocol MyProtocol {
associatedtype MyType
mutating func append(_ item: MyType)
var count: Int { get }
subscript(i: Int) -> MyType { get }
}
struct CustomIterator<Element>: MyProtocol {
...
subscript(i: Int) -> Element {
return elements[i]
}
var count: Int {
return elements.count
}
}
这个函数接受ite1
和ite2
两个参数。参数ite1
的类型为A1
,参数ite2
的类型为A2
。A1
和A2
是容器的两个占位类型参数,函数被调用时才能确定它们的具体类型。
这个函数的类型参数列表还定义了对两个类型参数的要求:
A1
必须符合MyProtocol
协议(写作A1: MyProtocol
)。A2
必须符合MyProtocol
协议(写作A2: MyProtocol
)。A1
的MyType
必须和A2
的MyType
类型相同(写作A1.MyType == A2.MyType
)。A1
的MyType
必须符合Equatable协议(写作A1.MyType: Equatable
),同时也就意味着A2.MyType
也必须遵循Equatable
协议。
下面演示了isAllItemsMatch
函数的使用:
let cusIte1 = CustomIterator(elements: [1, 2, 3, 4])
let cusIte2 = CustomIterator(elements: [1, 2, 3, 4])
let cusIte3 = CustomIterator(elements: [1, 2, 3])
let cusIte4 = CustomIterator(elements: [1.1, 2.2, 3.3])
let cusIte5 = CustomIterator(elements: ["a", "b", "c"])
// true
print(isAllItemsMatch(cusIte1, cusIte2))
// false
print(isAllItemsMatch(cusIte1, cusIte3))
// 报错,类型不符
//print(isAllItemsMatch(cusIte1, cusIte4))
// 报错,类型不符
//print(isAllItemsMatch(cusIte1, cusIte5))
// 报错,类型不符
//print(isAllItemsMatch(cusIte4, cusIte5))
还有一种用法,就是在扩展泛型协议的时候,对关联类型加上约束:
// 两个测试协议
protocol TestProtoclol1 {}
protocol TestProtoclol2 {}
// 遵循两个测试协议的 测试类
class TestClass1: TestProtoclol1 {}
class TestClass2: TestProtoclol2 {}
// 泛型协议
protocol TestContainer {
associatedtype ItemType
}
// 扩展泛型协议,并使泛型 ItemType 遵循 TestProtoclol1 协议
extension TestContainer where ItemType: TestProtoclol1 {
var id: Int {
return 0
}
}
// 扩展泛型协议,并使泛型 ItemType 遵循 TestProtoclol2 协议
extension TestContainer where ItemType: TestProtoclol2 {
var id: Int {
return 1
}
}
// 遵循泛型协议的测试类1
class TestTest1: TestContainer {
typealias ItemType = TestClass1
// 换为 TestClass2 ,测试一下,看看结果
// typealias ItemType = TestClass2
}
// 遵循泛型协议的测试类2
class TestTest2: TestContainer {
typealias ItemType = Int
}
let test1 = TestTest1()
// 打印:0
print(test1.id)
let test2 = TestTest2()
// 报错,调用不到,因为 Int 并没有遵循 TestProtoclol1 或 TestProtoclol2 协议
//print(test2.id)
上述例子,简单地解释就是,泛型协议扩展中的属性或函数,如果扩展对泛型做了其他的约束,想要调用到扩展中的属性或函数,泛型就必须满足这个约束。
如例子中,test1
能获取到id
属性,是因为指定了ItemType
为TestClass1
,TestClass1
是遵循了TestProtoclol1
协议的,所以获取到的id
的值就是TestContainer
第一个扩展中的id
值0
,test2
无法获取id
值,是因为指定了ItemType
为Int
类型,而Int
并未遵循TestProtoclol1
或TestProtoclol2
协议。
看明白了这个例子,就可以思考一下,我们使用RxSwift
时,调用rx
的方法,例如:button.rx.tap...
,总会有.rx
这种签名的标识,RxSwift
是怎么做到呢,原理就是关联类型和对泛型的扩展。
我们来尝试实现一下,我想调用自己对String
和URL
的扩展方法带上.shanzhu
的标识:
public struct Shanzhu<Base> {
public let base: Base
public init(_ base: Base) {
self.base = base
}
}
public protocol ShanzhuCompatible {
// 关联类型
associatedtype CompatibleType
// 实例
var shanzhu: Shanzhu<CompatibleType> { get set }
// 静态
static var shanzhu: Shanzhu<CompatibleType>.Type { get set }
}
public extension ShanzhuCompatible {
var shanzhu: Shanzhu<Self> {
get {
return Shanzhu(self)
}
set {}
}
static var shanzhu: Shanzhu<Self>.Type {
get {
return Shanzhu<Self>.self
}
set {}
}
}
extension String: ShanzhuCompatible {}
extension URL: ShanzhuCompatible {}
// String 的扩展
extension Shanzhu where Base == String {
func exTest() {
print("String exTest suc~~ value:\(self)")
}
}
// URL 的扩展
extension Shanzhu where Base == URL {
func exTest() {
print("URL exTest suc~~ value:\(self)")
}
}
// 打印结果:String exTest suc~~ value:Shanzhu<String>(base: "a")
"a".shanzhu.exTest()
// 打印结果:URL exTest suc~~ value:Shanzhu<URL>(base: http://www.baidu.com)
URL(string: "http://www.baidu.com")?.shanzhu.exTest()
// 不带 .shanzhu 的标识,调用不到,为什么呢?
//"a".exTest()
//URL(string: "a")?.exTest()
这段代码的意思:
- 定义了一个名为
Shanzhu
的结构体,且定义了泛型Base
,结构体有一个属性base
为Base
类型,并提供一个初始化方法init
;- 定义了一个泛型协议
ShanzhuCompatible
,关联类型为CompatibleType
,且有都名为shanzhu
的一个实例变量和一个静态变量,均为可读可写,类型都是Shanzhu<CompatibleType>
结构体;- 扩展了
ShanzhuCompatible
泛型协议,并实现了名为shanzhu
的一个实例变量和一个静态变量,get
方法返回对应的实例变量和静态变量;String
和URL
遵循ShanzhuCompatible
泛型协议;- 扩展
Shanzhu
结构体,分别约束泛型Base
为String
和URL
,都添加一个exTest
方法。
以String
为例,"a".shanzhu.exTest()
能调用成功:
"a"
是String
的实例,由于String
遵循了ShanzhuCompatible
协议,所以就有一个shanzhu
的实例属性;- 当
"a".shanzhu
时,ShanzhuCompatible
的关联类型CompatibleType
就会推导出是String
类型,同时也就会推导出shanzhu
实例属性是Shanzhu<String>
类型;- 由于我们扩展了
Shanzhu
结构体,且约束了Base
为String
类型,其中有一个exTest
方法,那么Shanzhu<String>
类型恰好是满足条件的,所以能调用到exTest
方法。
URL也是一个道理,就不详细说明了。
分析完了,有没有一种代理的感觉,没错,就是代理,我们并不是直接对String
或URL
做的扩展吧,而是对Shanzhu
结构体进行扩展并约束泛型Base
为String
类型,为了能调用到扩展中的方法或属性,我们又让String
遵循了ShanzhuCompatible
协议,这个协议中有Shanzhu
结构体的实例变量和静态变量,而协议的关联类型就是Shanzhu
结构体的泛型类型,当.shanzhu
时,就能调用到Shanzhu
结构体的扩展中与当前具体类型一致的方法或属性。
说到这,不带.shanzhu
的标识,调用不到的原因就很清晰了。
D. 泛型下标
下标能够是泛型的,他们能够包含泛型where
子句。你可以把占位符类型的名称写在 subscript
后面的尖括号里,在下标代码体开始的标志的花括号之前写下泛型where
子句。
extension CustomIterator {
subscript<Indices: Sequence>(indices: Indices) -> [Element]
where Indices.Iterator.Element == Int {
var result = [Element]()
for index in indices {
result.append(self[index])
}
return result
}
}
这个CustomIterator
的扩展添加了一个下标方法,接收一个索引的集合,返回每一个索引所在的值的数组。这个泛型下标的约束如下:
这个CustomIterator
的扩展添加了一个下标:下标是一个序列的索引,返回的则是索引所在的项目的值所构成的数组。这个泛型下标的约束如下:
- 在尖括号中的泛型参数
Indices
,必须是符合标准库中的Sequence
协议的类型。- 下标使用的单一的参数,
indices
,必须是Indices
的实例。- 泛型
where
子句要求Sequence(Indices)
的迭代器,其所有的元素都是Int
类型。这样就能确保在序列(Sequence)
中的索引和容器(Container)
里面的索引类型是一致的。
综合一下,这些约束意味着,传入到indices
下标,是一个整型的序列。
我们来看一下使用效果:
let subIte = CustomIterator(elements: [1, 2, 3, 4])
// 打印结果:[1, 3]
print(subIte[[0, 2]])
使用泛型下标后,使用下标访问数组元素时,就不仅限于访问某一个,而是可以访问一组了。
到这,本篇文章要说的就是这些了,泛型远远不止这些内容,本篇文章算是一个抛砖引玉吧,如有说的不准确的地方,欢迎指正~
--
参考文档:
https://github.com/ReactiveX/RxSwift/blob/master/RxSwift/Reactive.swift
http://www.swift51.com/swift4.0/chapter2/23_Generics.html
https://swift.gg/2018/08/28/swift-generics/
https://www.cnblogs.com/ming1025/p/6072715.html
https://www.cnblogs.com/pengser/p/4986657.html