前言
-
在 Swift 语言中,泛型可以说是用的最广最强大的特性之一,因为在 Swift 语言本身的语言底层大量的使用了泛型。
-
泛型使得同样的操作可以应用于不同的数据类型。
- 泛型编程的实现是我们程序在另一种抽象层次上的提升。
- 类是现实世界事物的抽象,而泛型则是现实世界行为的抽象。
1、节点泛型
-
Swift 中的泛型同其它语言相同,用一对尖括号
< >
来声明泛型,尖括号中通常使用T
、U
、V
等这样的大写字母来表示 “节点” 类型。 -
使用泛型作为参数的函数叫做泛型函数。
-
泛型函数在声明时使用节点类型命名来替代实际的类型名,并在泛型函数名后面插入节点类型的声明。
-
节点类型在定义时不表示任何具体类型,在函数被调用时会根据传入的实际类型来指定自身的类型。
func 函数名<T>(参数名1: T, 参数名2: T, ...) -> T { 函数体 ..... return 返回值 }
-
-
1)如果函数的列表中只有一个字母,如
T
,虽然具体的类型不需要指定,但是函数中每个节点类型的参数(函数参数或返回值类型)必须是相同类型的。func show<T>(para1: T, para2: T) { print("(para1)" + " (para2)") }
// 在调用时两个参数必须是相同的 show(para1: 1, para2: 2) // 1 2 show(para1: "xiaoming", para2: "xiaobai") // xiaoming xiaobai
-
2)如果要定义多个不同类型的泛型,则需要在尖括号中加入多个节点
<T, U, V ...>
。func show<T, U>(para1: T, para2: U) { print("(para1)" + " (para2)") }
// 在调用时两个参数可以不同 show(para1: "xiaoming", para2: 18) // xiaoming 18
-
3)你还可以对节点进行一些限制,比如要求泛型遵守某些协议。
// Swift 中数组的判等函数 public func ==<Element: Equatable>(lhs: [Element], rhs: [Element]) -> Bool {...}
Element
是使用节点声明的,它代表一个泛型,可以看到这里的泛型名是Element
,相比上面的T
、U
、V
等要长的多。这是因为此处的Element
不仅仅是一个占位符的作用,它还声明了这个泛型代表数组中的元素类型,具有具体的意义。
-
4)有时候节点中的泛型需要有更多的限制,需要使用
where
子句来补充约束条件。-
在 Swift 3.0 之前。
func anyCommonElements<T: SequenceType, U: SequenceType where T.Generator.Element: Equatable, T.Generator.Element == U.Generator.Element>(lhs: T, _ rhs: U) -> Bool { ... }
-
在 Swift 3.0 及之后
where
子句被移动到了参数列表的后面。func anyCommonElements<T: SequenceType, U: SequenceType>(lhs: T, _ rhs: U) -> Bool where T.Generator.Element: Equatable, T.Generator.Element == U.Generator.Element{ ... }
-
2、泛型协议
-
1)除了节点式声明泛型外,还有其它方式声明一个泛型,比如使用关键字
associatedtype
(关联类型)(Swift 3.0 之前是typealias
关键字)。protocol SomeProtocol { associatedtype Element func elementMethod1(element: Element) func elementMethod2(element: Element) }
-
这里虽然没有出现节点语法,但上面的协议确实是个不折不扣的泛型协议,
Element
起到了占位符的作用,指示了某种类型。 -
根据协议的规则,协议
SomeProtocol
的遵守者必须实现上面两个方法,Element
隐式的约束了两个方法的参数必须是相同类型的。 -
不用刻意指定
Element
的具体类型,编译器会根据实现方法时传入的参数类型确定Element
的具体类型。 -
在实现的时候不能直接用
Element
,Element
只能存在于具体实现之前。泛型协议依靠遵守协议中协议方法的的具体实现来明确泛型的类型。 -
协议的遵守者如果遵守了多个协议,那么这些协议的关联类型名称不能重复,否则编译器会报错。
struct TestStruct: SomeProtocol { func elementMethod1(element: String) { print("elementFromMethod1: (element)") } func elementMethod2(element: String) { print("elementFromMethod2: (element)") } }
TestStruct().elementMethod1(element: "qwert") // elementFromMethod1: qwert TestStruct().elementMethod2(element: "asdfg") // elementFromMethod2: asdfg
-
-
2)类似于
associatedtype
的还有Self
关键字,代表了协议遵守者本身的的类型,适用于 “比较” 这类方法,其必须传入另一个相同类型的参数才有意义。protocol CanCompare { func isBigger(other: Self) -> Bool }
struct BoxInt: CanCompare { var intValue: Int func isBigger(other: BoxInt) -> Bool { return self.intValue > other.intValue } }
BoxInt(intValue: 3).isBigger(other: BoxInt(intValue: 2)) // true
3、泛型对象
-
关联类型和
Self
关键字都是在协议层面的泛型,此外还有对象层面的泛型,比如我们常用的数组就是对象层面的泛型定义的,效果相同。 -
如果不使用协议,一个泛型对象风格的结构体定义如下。
struct TestStruct<T: Comparable> { func elementMethod1(element: T) { print("elementFromMethod1: (element)") } func elementMethod2(element: T) { print("elementFromMethod2: (element)") } }
-
泛型应该用在声明中,调用时版本中的泛型已经被 “特化” 成具体的泛型。泛型对象通过构造器初始化时明确明确泛型的类型,这些类型都是具体的。
-
如果在实现时 “嵌套” 一个泛型,那么会导致泛型无法特化。比如数组本身是泛型的,在声明数组类型时传入了另一个泛型,那么你将无法初始化该数组。
let test = TestStruct<Int>() test.elementMethod1(1)
-
4、泛型方法
-
方法中的泛型使用节点表示法,作用域只在本方法内。
struct TestStruct { func elementMethod1<T: Comparable>(element: T) { print("elementFromMethod1: (element)") } func elementMethod2<T: Comparable>(element: T) { print("elementFromMethod2: (element)") } }
-
这样同一个实例的相同方法就可以接受不同的参数类型了。
let test = TestStruct() test.elementMethod1(element: 1) test.elementMethod1(element: "abc")
-
5、协议中的 where 关键字
-
where
关键字可以与for-in
循环配合实现筛选,where
同样可以用在协议扩展中,使得定义在协议扩展中的方法可以对某些遵守者 “隐身”,而对另一些遵守者 “可见”。 -
协议的遵守者如果遵守了多个协议,那么这些协议的关联类型名称不能重复,否则编译器会报错。
protocol SomeProtocol { associatedtype OwnElement func elementMethod1(element: OwnElement) func elementMethod2(element: OwnElement) }
-
1)
where
关键字可以与Self
关键字配合,在协议扩展中定义一个新方法,指定当协议的遵守者是集合类型时,必须打印出遵守者的元素个数。extension SomeProtocol where Self: Collection { func showCount() { print(self.count) } }
-
where
的限制是强制的,他是一个编译器强制的开关。虽然在协议的扩展中并不能知晓会有哪些对象遵守了该协议,但是一旦使用了where
,那么就表明在扩展的作用域中where
后面的语句默认实现了。 -
Array
类型已经遵守了Collection
协议,所以如果让Array
同时遵守SomeProtocol
,那么它就能获得showCount
方法。extension Array: SomeProtocol { func elementMethod1(element: String) { print("elementFromMethod1: (element)") } func elementMethod2(element: String) { print("elementFromMethod2: (element)") } }
[1, 2, 3].showCount() // 3
-
-
2)
where
所指定的力度范围还可以继续缩小,可以使用where
限定协议中的关联类型。extension SomeProtocol where OwnElement: SignedNumeric { func showElement() { print(OwnElement.self) } }