「这是我参与2022首次更文挑战的第22天,活动详情查看:2022首次更文挑战」。
- 本文主要介绍swift中泛型的介绍。
1. 范型的定义
我们看下下面的方法,是一个非泛型函数,主要计算2个数的和
func addNumInt(_ x: Int, _ y: Int) -> Int{
return x + y
}
但是它只能用于 Int 值。如果我们想计算两个 Double 或者 其他类型的和的值,我们需要在写一些函数,比如 addNumDouble(_:_:) 函数:
func addNumDouble(_ x: Double, _ y: Double) -> Double{
return x + y
}
但是我们可以看到,对于上面2个方法函数的计算方式都是一样的,唯一的区别就是传入和返回的类型不同(Int,Double)。
因此我们想找到一个可以计算任意类型值的函数怎么办?泛型正是可以让我们做出这样函数的语法。
1.1 范型的写法
我们先来看一下泛型的基本写法 ,首先我我们要指定一个占位符 T ,紧挨着写在函数名后面的 一对尖括号(当前我们这个 T 要遵循 FloatingPoint 协议,计算加法所必须);其次我们就可以 使用 T 来替换任意定义的函数形式参数
func addNum<T:FloatingPoint>(_ x: T, _ y: T) -> T{
return x + y
}
结构体中使用 这是一个栈的数据结构,其 中数据结构中的元素类型是Int,看一个mutating使用的列子。
struct KBStack{
private var items = [Int]()
mutating func push(_ item: Int){
items.append(item)
}
mutating func pop()->Int?{
if items.isEmpty {
return nil
}
return items.removeLast()//移除数组最后一个并返回
}
}
这是一个非泛型的数据结构,我们改造一下
struct KBStack<T>{
private var items = [T]()
mutating func push(_ item: T){
items.append(item)
}
mutating func pop()->T?{
if items.isEmpty {
return nil
}
return items.removeLast()//移除数组最后一个并返回
}
}
do{
var stack = KBStack<Double>()
stack.push(10.0)
stack.push(20.0)
print(stack.pop() ?? 0)//打印是20.0
}
基本上和栈的数据结构相关都有相同的操作,这个时候我们免不了抽取相同的行为定一个协议
protocol StackProtocol {
var itemCount: Int{ get }
mutating func pop() -> Int?
func index(of index: Int) -> Int
}
1.2 关联类型
这里我们定义协议的时候需要指明当前类型,那么能不能给 Protocol 也上一个泛型?,这样一些就会出现一个错误,系统提示 Protocol 不支持泛型参数,需要我们使用关联类型来代替。
关联类型给协议中用到的类型一个占位符名称,协议定义中的相关类型我们都可以用这个占位符 替代
等到真正实现协议的时候在去确定当前占位符的类型,比如下面的代码
protocol StackProtocol {
associatedtype Item
var itemCount: Item{ get }
mutating func pop() -> Item?
func index(of index: Int) -> Item
}
struct KBStack: StackProtocol{
typealias Item = Int
private 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]
}
}
1.3 添加约束where
我们也可以给当前关联的对象添加约束,比如我们要求T必须都要遵循 FixWidthInteger
直接在约束中使用协议
在这个协议中,Even是一个关联类型,类似上面的Item,Even有2个约束,首先它要遵循当前协议,同时它的Item类型必须和容器里面的Item类型相同,这里的约束使用where分居。
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: Int(i)) != stack2.index(of: Int(i)){
return false
}
}
return true
}
这个函数有两个形式参数, stack1 和 stack2 。 stack1 形式参数是 T1 类型, stack2 形式参 数是 T2 类型。 T1 和 T2 是两个容器类型的类型形式参数,它们的类型在调用函数时决定。 下面是函数的两个类型形式参数上设置的要求:
-
T1 必须遵循 StackProtocol 协议(写作 T1: StackProtocol );
-
T2 也必须遵循 StackProtocol 协议(写作 C2: StackProtocol );
-
T1 的 Item 必须和 T2 的 Item
相同(写作 T1.Item == C2.Item); -
T1 的 Item 必须遵循
Equatable协议(写作 T1.ItemType: Equatable )。 前两个要求定义在了函数的类型形式参数列表里,后两个要求定义在了函数的泛型Where分句中 -
这些要求意味着: stack1 是一个 T1 类型的容器;
-
stack2 是一个 T2 类型的容器;
-
stack1 和 stack2 中的元素
类型相同
2. 类型擦除
前面我们已经了解了什么是协议,什么是泛型。接下来我们了解一下什么是类型擦除。首先我们 先来看一个例子(这里我们定义了一个泛型协议)
protocol DataFetch {
associatedtype dataType
func fetch(completion: ((Result<dataType, Error>) -> Void)?)
}
紧接着我们需要请求一个用户数据
struct Person{
var name:String?
var age:Int?
}
struct PersonData:DataFetch {
typealias dataType = Person
func fetch(completion: ((Result<Person, Error>) -> Void)?) {
let p = Person(name: "jack", age: 18)
completion?(.success(p))
}
}
这时候我们需要使用我们当前的 PersonData 获取我们当前的 Person 数据
会报错,也就意味着
DataFetch 只能用作泛型约束,不能用作具体类型!因为编译器无法确定
dataType 的具体类型是什么。如果我们直接指定了,可以解决
但是如果我们这时候还有其它数据,比如VIP的数据这样就会耦合很多数据,违背了我们泛型的想法。
我们可以抽象一个中间层,来解决这样的问题
struct AnyDataFetch<T>: DataFetch {
typealias dataType = T
private let _fetch: (((Result<T, Error>) -> Void)?) -> Void
init<U: DataFetch>(_ fetchable: U) where U.dataType == T {
_fetch = fetchable.fetch
}
func fetch(completion: ((Result<dataType, Error>) -> Void)?) {
_fetch(completion)
}
}
这里我们定义了一个中间层结构体 AnyDataFetch , AnyDataFetch 实现了 DataFetch 的所 有方法。
-
在
AnyDataFetch的初始化过程中,实现协议的类型会被当做参数传入(依赖注入) -
在
AnyDataFetch实现的具体协议方法fetch中,再转发实现协议的抽象类型。
这个时候我们就可以把 AnyDataFetch 当做具体类型使用。
class someViewController{
let userData: AnyDataFetch<Person>
init(userData: AnyDataFetch<Person>) {
self.userData = userData
}
}
do{
let vipFetch = VIPPersonData()
let anyDataFetch = AnyDataFetch<Person>(vipFetch)
let vc = someViewController.init(userData: anyDataFetch)
//
let userData = PersonData()
let anyDataFetch1 = AnyDataFetch<Person>.init(userData)
let vc1 = someViewController(userData: anyDataFetch)
vc.userData.fetch {
(result) in
switch result {
case .success(let user):
print(user.name) case .failure(let error):
print(error) }
}
vc1.userData.fetch {
(result) in
switch result {
case .success(let user):
print(user.age) case .failure(let error):
print(error) }
}
}
相当于多了一个中间层,绕过编译器的检查