Swfit进阶-19-Swift中的泛型

214 阅读4分钟

「这是我参与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 不支持泛型参数,需要我们使用关联类型来代替。

image.png

关联类型给协议中用到的类型一个占位符名称,协议定义中的相关类型我们都可以用这个占位符 替代

image.png

等到真正实现协议的时候在去确定当前占位符的类型,比如下面的代码

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

image.png

直接在约束中使用协议

image.png

在这个协议中,Even是一个关联类型,类似上面的Item,Even有2个约束,首先它要遵循当前协议,同时它的Item类型必须和容器里面的Item类型相同,这里的约束使用where分居。

image.png

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 数据

image.png 会报错,也就意味着 DataFetch 只能用作泛型约束,不能用作具体类型!因为编译器无法确定

dataType 的具体类型是什么。如果我们直接指定了,可以解决

image.png

但是如果我们这时候还有其它数据,比如VIP的数据这样就会耦合很多数据,违背了我们泛型的想法

image.png

我们可以抽象一个中间层,来解决这样的问题

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)

    }

 
}

这里我们定义了一个中间层结构体 AnyDataFetchAnyDataFetch 实现了 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) }

    }

}

相当于多了一个中间层,绕过编译器的检查

image.png