一、泛型
来个例子:
func multiNumInt(_ x: Int, _ y: Int) -> Int{
return x * y
}
func multiNumDouble(_ x: Double, _ y: Double) -> Double{
return x * y
}
以上两个方法的函数体是一样的,区别在于入参的类型不同,泛型的使用可以让我们无视不同类型带来的冗余代码。
例子:
func multiNum<T:FloatingPoint>(_ x:T, _y:T) -> T{
return x * y
}
泛型的基本写法 ,首先指定一个占位符
T,紧挨着写在函数名后面的一对尖括号(表示这个T要遵循某个协议),这时可以使用T来替换任意定义的函数形式参数。
在结构体和类中也可以使用泛型
class Stack<E> {
var elements = [E]()
func push(_ element:E) {
elements.append(element)
}
func pop() -> E{
elements.removeLast()
}
func top() -> E{
elements.last!
}
func size() -> Int {
elements.count
}
}
使用:
var stack = Stack<Int>()
stack.push(10)
stack.pop(5)
var stack2 = Stack<Float>()
stack2.push(50.1)
stack2.pop(10.1)
可以在class中默认给个初始化方法,直接传入值就好,无需指定类型
init(firstElement:E){
elements.append(firstElement)
}
使用:
var stack3 = Stack(firstElement: 99)
子类继承自带有泛型的父类时,也需要加上泛型
class subStack<E>:Stack<E>{
}
二、关联类型 associatedtype
在swift的协议中,是不支持泛型写法的,需要配合 associatedtype 关键字才能实现。
关联类型的作用是给协议中用到的类型定义一个占位名称,并且协议中可以拥有多个关联类型。
protocol Stackable {
associatedtype Element //关联类型
mutating func push(_ element: Element)
mutating func pop() -> Element
func top() -> Element
func size() -> Int
}
注意 push 和 pop 在前面用 mutating 修饰,这是为了给值类型也可以遵守这份协议,因为这两个方法都是要修改 Stack<E> 的内存。
接下来我们可以用一个泛型类型来遵守 Stackable,通过 typealias 给关联类型设定了一个类型,这个类型就是泛型E
class Stack<E>: Stackable {
typealias Element = E //typealias 指定 Element 的类型, 这句话也可以注释掉
var elements = [E]()
func push(_ element: E) { elements.append(element) }
func pop() -> E{ elements.removeLast() }
func top() -> E{ elements.last! }
func size() -> Int { elements.count }
}
三、类型约束
1、可以在associatedtype后面指定必须遵循的协议
protocol Stackable {
associatedtype Element<FixedWidthInteger> //关联类型 指定遵循某个协议
mutating func push(_ element: Element)
mutating func pop() -> Element
func top() -> Element
func size() -> Int
}
2、直接在协议中使用约束
protocol EvenProtocol: Stackable{
associatedtype Even: EvenProtocol where Even.Element == Element //约束
func pushEven(_ item: Int) -> Even
}
//使用
extension LGStack: EvenProtocol{
func pushEven(_ item: Int) -> LGStack {
var result = LGStack()
if item % 2 == 0{
result.push(item)
}
return result
}
}
在这个协议里,Even 是一个关联类型,Even 拥有两个约束:
- 它必须遵循
EvenProtocol协议(就是当前定义的协议)。- 以及它的
Element类型必须是和容器里的Element类型相同。
3、在函数中使用约束
func compare<T1: Stackable, T2: Stackable>(_ stack1: T1, _ stack2: T2) -> BOOL where T1.Element == T2.Element, T1.Element:Equatable {
guard stack1.size == stack2.size else {
return false
}
return true
}
这个函数有两个形式参数:stack1 和 stack2,stack1是T1类型,stack2是T2类型,T1和T2的具体类型在函数调用时决定。
约束要求:
T1、T2都必须遵守Stackable协议。T1的Element与T2的Element相同。T1的Element遵守Equatable协议。
这些约束意味着:
stack1是一个T1类型的容器。stack2是一个T2类型的容器。stack1和stack2中的元素类型相同。
四、 类型擦除
通过两个案例来说明白这个类型擦除是什么玩意儿
案例一: 声明一份协议DataFetch,协议里有一个关联类型DataType,这份协议是用来提取数据的。
// 数据提取器
protocol DataFetch {
/// 关联类型
associatedtype DataType
/// 提取数据的方法
func fetch(completion: Body<DataType>?)
}
fetch()方法接收一个函数 Body,这个函数的定义如下:
typealias Body<T> = (Result<T, Error>) -> ()
这个函数接收一个 Result 类型的参数,并且它支持是一个泛型参数 T, T 可以是任意类型。
这么做的目的在于:首先这是一份协议,意味着它会被多个类型遵守,每个实现 fetch(completion:) 的代码是不一样的,也就是提取数据的方式不一样。
当一个类中包含了一个遵守 DataFetch 协议类型的变量,但这个变量的类型并不是单一的,而希望它支持遵守了 DataFetch 协议的其它类型,我们不需要关心数据是如何提取的,只关心提取数据后的结构。此时我们把这个变量当作该类的一个属性或者一个方法中的参数,第一时间想到的是用 DataFetch 作为类型,代码如下:
class SomeViewController {
let dataFetch: DataFetch
init(_ dataFetch: DataFetch){
self.dataFetch = dataFetch
}
}
但此时编译器会报错:
也就是说,协议
DataFetch只能用作通用约束,当协议中有用到关联类型和 Self 的时候,协议就不能当作类型来使用。因为编译器不知道协议中的这个关联类型的具体类型是什么。
那这个时候就可以用到类型擦除的技术了
类型擦除是一种非常有用的技术,用来阻止泛型对代码的侵入,也可用来保证接口简单明了。通过将底层类型包装起来,将API与具体的功能进行拆分。
- 定义一个中间层类型,该类型实现了协议
DataFetch中的所有方法。 - 在中间层类型中实现的具体协议方法里转发给实现协议的抽象类型。
- 在中间层类型的初始化方法中把抽象类型当参数传入(依赖注入)。
struct AnyDataFetch<T>: DataFetch {
private let _fetch: (Body<T>?)-> ()
init<U: DataFetch>(_ fetchable: U) where U.DataType == T {
_fetch = fetchable.fetch
}
func fetch(completion: Body<T>?) {
_fetch(completion)
}
}
这个中间层命名为 AnyDataFetch,它遵守 DataFetch 协议,并且实现了协议中的所有方法。这个方法里面就只干一件事,调用协议的抽象类型的 fetch(completion:) 方法。协议的抽象类型的 fetch(completion:) 我们用一个属性去接收。
因为要接收协议的抽象类型的 fetch(completion:) 方法,所以我们要在初始化的时候把协议的抽象类型的实例传进来,然后用 _fetch 去接收。需要注意的是这里要求协议的抽象类型中的 DataType 必须和泛型 T 相同。
此时这个中间层的结构就完成了,接下来是这个抽象类型的定义,我把它命名为 UserData,它用来提取用户的数据,代码如下:
struct UserData: DataFetch {
func fetch(completion: Body<Model>?) {
completion?(.success(Model(id: 1001, other: "其他")))
}
}
协议的抽象类型实现了 fetch(completion:) 方法后,我们在里面简单的写了一个提取成功的回调,这个可以是任意类型,为了方便我就把它写成了一个 Model,Model 的代码如下:
struct Model {
var id: Int
var other: Any
}
最后 controller 就可以这么去用了:
typealias Body<T> = (Result<T, Error>) -> ()
protocol DataFetch {
associatedtype DataType
func fetch(completion: Body<DataType>?)
}
struct AnyDataFetch<T>: DataFetch {
private let _fetch: (Body<T>?)-> ()
init<U: DataFetch>(_ fetchable: U) where U.DataType == T {
_fetch = fetchable.fetch
}
func fetch(completion: Body<T>?) {
_fetch(completion)
}
}
struct Model {
var id: Int
var other: Any
}
//user的数据
struct UserData: DataFetch {
func fetch(completion: Body<Model>?) {
completion?(.success(Model(id: 1001, other: "其他")))
}
}
//teacher的数据
struct TeacherData: DataFetch {
func fetch(completion: Body<Model>?) {
completion?(.success(Model(id: 1002, other: "llllll")))
}
}
class SomeViewController {
let dataFetch: AnyDataFetch<Model>
init(_ dataFetch: AnyDataFetch<Model>){
self.dataFetch = dataFetch
loadData()
}
func loadData() {
self.dataFetch.fetch { body in
switch body {
case .success(let model):
print("success:\(model)" )
case .failure(let error):
print("failure:\(error)" )
}
}
}
}
let userData = UserData()
let teacherData = TeacherData()
let userDataFetch = AnyDataFetch<Model>.init(userData)
let teacherDataFetch = AnyDataFetch<Model>.init(teacherData)
let someVC1 = SomeViewController(userDataFetch)
let someVC2 = SomeViewController(teacherDataFetch)