泛型
泛型代码能根据所定义的要求写出可以用于任何类型的灵活的、可复用的函数。可以编写出 可复用、意图表达清晰、抽象的代码
。
泛型
是Swift最强大的特性之一,很多Swift标准库
是基于 泛型
代码构建的。如,Swift 的 Array和Dictionary类型都是泛型集合
;你也可以创建一个容纳 Int
值的数组,或者容纳 String
值的数组,甚至容纳任何 Swift
可以创建的其他类型的数组。同样,可以创建一个存储任何指定类型值的字典,而且类型没有限制。
泛型所解决的问题:代码的复用性和抽象能力
。比如,交换两个值,这里的值可以是 Int、Double、String
。
1 2 3 4 5 6
| // 经典例子swap,使用泛型,可以满足不同类型参数的调用 func swap<T>(_ a: inout T, _ b: inout T){ let tmp = a a = b b = tmp }
|
基础语法
主要讲3点:类型约束、关联类型、Where语句
在一个 类型参数后面放置协议或者是类
,例如下面的例子,要求 类型参数T
遵循 Equatable
协议。
1 2 3 4 5 6 7
| func test<T:Equatable>(_ a: T, _ b: T) -> Bool { return a == b } test(1, 2) // 打印结果 false test("A", "a") // 打印结果 false
|
Equatable协议:可以比较值相等的协议,即可以使用 ==
比较
在定义协议时,使用 关联类型
给 协议
中用到的 类型
起一个 占位符名称
。关联类型 只能用于协议
,并且是通过关键字 associatedtype
指定。
下面这个示例,仿写的一个栈的结构体:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| struct Stack { var items = [Int]() // 入栈 mutating func push(_ item: Int) { items.append(item) } // 出栈 mutating func pop() -> Int?{ if items.isEmpty { return nil } return items.removeLast() } }
|
该结构体中有个成员 item
,是个只能存储 Int
类型的数组,如果想使用其他类型呢? 可以通过协议来实现 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| protocol StackProtocol { // 协议中使用类型的占位符 associatedtype Item }
struct Stack: StackProtocol{ // 在使用时,需要指定具体的类型 typealias Item = Int var items = [Item]() // 入栈 mutating func push(_ item: Int) { items.append(item) } // 出栈 mutating func pop() -> Int?{ if items.isEmpty { return nil } return items.removeLast() } }
|
我们在尝试用 泛型
实现上面的功能:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| // 泛型实现 struct Stack<T> { var items = [T]() // 入栈 mutating func push(_ item: T) { items.append(item) } // 出栈 mutating func pop() -> T?{ if items.isEmpty { return nil } return items.removeLast() } }
|
泛型的优势和强大,暴露无疑
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
| // where protocol StackProtocol { // 协议中使用类型的占位符 associatedtype Item // 实例属性 var itemCount: Int { get } // 出栈 mutating func pop() -> Item? // 下标获取 func index(of index: Int) -> Item } struct Stack: StackProtocol { // 在使用时,需要指定具体的类型 typealias Item = Int // 存储属性 var items = [Item]() // 计算属性 var itemCount: Int { get { return items.count } } // 入栈 mutating func push(_ item: Item){ items.append(item) } // 出栈 mutating func pop() -> Item?{ if items.isEmpty { return nil } return items.removeLast() } // 下标获取 func index(of index: Int) -> Item{ return items[index] } } /* whrer语句 T1.Item == T2.Item 表示 T1 和 T2 中的类型必须相等 T1.Item: Equatable 表示 T1 的类型必须遵循 Equatable 协议,意味着 T2 也要遵循 Equatable协议 */ func compare<T1: StackProtocol, T2: StackProtocol>(_ stack1: T1,_ stack2: T2) -> Bool where T1.Item == T2.Item, T1.Item: Equatable { // 如果数量相等,则遍历 guard stack1.itemCount == stack2.itemCount else { return false } // 遍历 for i in 0..<stack1.itemCount { if stack1.index(of: i) != stack2.index(of: i) { return false } } return true }
|
还可以这么写:
1 2
| // 扩展协议中的 Item 遵循 Equatable extension StackProtocol where Item: Equatable {}
|
当希望 泛型指定类型
拥有特定功能,可以这么写,在上述写法的基础上 增加extension:
1 2
| // 扩展协议中 Item 指定具体类型 extension StackProtocol where Item == Int {}
|
泛型函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| // 简单的泛型函数 func testGen<T>(_ value: T) -> T { let tmp = value return tmp } class Teacher { var age: Int = 18 var name: String = "ZJ" } // 传入Int类型 testGen(10) // 传入元组 testGen((1,2)) // 传入实例对象 testGen(Teacher())
|
从以上代码可以看出,泛型函数 可以接受任何类型
。
总结
- 泛型主要用于解决代码的
抽象能力
,以及提升代码的 复用性
- 如果一个泛型
遵循了某个协议
,则在使用时,要求具体的类型也是必须遵循某个协议的;
- 在定义协议时,可以使用
关联类型
给协议中用到的 类型
起一个 占位符名称
;
where语句
主要用于表明泛型需要满足的条件,即 限制形式参数的要求
。