Swift 泛型介绍

·  阅读 146
Swift 泛型介绍

「这是我参与2022首次更文挑战的第5天,活动详情查看:2022首次更文挑战」。

为什么会有泛型

下面的 multiNumInt ( _ : _ :) 是个非泛型函数,主要用于计算两个数的乘积

func multiNumInt(_ x: Int, _ y: Int) -> Int { 
    return x * y
}

multiNum(_:_:) 函数很实用,但是它只能用于 Int 值。如果我们想计算两个 Double 或者其他类型的乘积的值,我们需要在写一些函数,比如 multiNumDouble(_:_:) 函数:

func multiNumDouble(_ x: Double, _ y: Double) -> Double { 
    return x * y
}

但是我们发现, 函数体是一样的。唯一的区别是它们接收值类型不同 ( IntDouble)。

如果是上面这种情况的话,我们在代码书写的时候就会有很多冗余代码,这个时候如果我们想找到一个可以计算任意类型值的函数,那么泛型正是能让我们写出这样函数的语法。

泛型语法注意要点

我们先来看一下泛型的基本写法,首先我我们要指定一个占位符 T ,紧挨着写在函数名后面的 一对尖括号(当前我们这个 T 要遵循 FloatingPoint 协议,计算乘积所必须);其次我们就可以使用 T 来替换任意定义的函数形式参数。

func multiNum<T: FloatingPoint>(_ x: T, _ y: T) -> T { 
    return x * y
}

下面我们来看一个例子,这是一个栈的数据结构,其中数据结构中的元素类型是 Int

struct LGStack  {
    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 LGStack<Element> {
    private var items = [Element]()

    mutating func push(_ item: Element){ 
        items.append(item)
    }
    mutating func pop() -> Element?{ 
        if items.isEmpty { return nil } 
        return items.removeLast()
    } 
}

基本上和栈的数据结构相关都有相同的操作,这个时候我们可以抽取相同的行为定一个协议

protocol StackProtocol {
    var itemCount: Int{ get }
    mutating func pop() -> Int?
    func index(of index: Int) -> Int 
}

这里我们定义协议的时候需要指明当前类型,那么能不能给 Protocol 也加上一个泛型呢?

这里我们这样写的话就会出现一个错误,系统提示 Protocol 不支持泛型参数,需要我们使用关联类型来代替。

protocol StackProtocol { 
    associatedtype Item
    var itemCount: Item{ get }
    mutating func pop() -> Item?
    func index(of index: Int) -> Item 
}

关联类型给协议中用到的类型一个占位符名称,协议定义中的相关类型我们都可以用这个占位符 替代,等到真正实现协议的时候在去确定当前占位符的类型,比如下面的代码:

protocol StackProtocol {
    associatedtype Item
    var itemCount: Int{ get }
    mutating func pop() -> Item?
    func index(of index: Int) -> Item 
}

struct LGStack: 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]
    } 
}

同样的我们也可以给当前的关联类型添加约束,比如我们要求 Item 必须都要遵循 FixWidthInteger

protocol StackProtocol {
    associatedtype Item: FixedWidthInteger

    var itemCount: Item{ get }

    mutating func pop() -> Item?

    func index(of index: Int) -> Item 
}

当然我们也可以直接在约束中使用协议

protocol EvenProtocol: StackProtocol {
    associatedtype Even: EvenProtocol where Even.Item == Item 
    func pushEven(_ item: Int) -> Even
}

在这个协议里,Even 是一个关联类型,就像上边例子中 StackProtocolItem 类型一样。 Even 拥有两个约束:它必须遵循 EvenProtocol 协议(就是当前定义的协议),以及它的 Item 类型必须是和容器里的 Item 类型相同。Item 的约束是一个 where 分句。

extension LGStack: EvenProtocol {
    func pushEven(_ item: Int) -> LGStack {
        var result = LGStack() 
        if item % 2 == 0 {
            result.push(item)
        }
        return result 
    }
}

在上面的例子中我们出现了一个 where 分句,泛型 Where 分句要求了关联类型必须遵循指定的协议,或者指定的类型形式参数和关联类型必须相同。泛型 Where 分句以 Where 关键字开 头,后接关联类型的约束或类型和关联类型一致的关系。

func compare<T1: StackProtocol, T2: StackProtocol>(_ stack1: T1, _ stack2:T2) {
    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
}

这个函数有两个形式参数,stack1stack2stack1 形式参数是 T1 类型,stack2 形式参 数是 T2 类型。T1T2 是两个容器类型的类型形式参数,它们的类型在调用函数时决定。

下面是函数的两个类型形式参数上设置的要求:

  • T1 必须遵循 StackProtocol 协议(写作 T1: StackProtocol );
  • T2 也必须遵循 StackProtocol 协议(写作 T2: StackProtocol );
  • T1Item 必须和 T2Item 相同(写作 T1.Item == T2.Item);
  • T1Item 必须遵循 Equatable 协议(写作 T1.ItemType: Equatable )。

前两个要求定义在了函数的类型形式参数列表里,后两个要求定义在了函数的泛型 Where 分句中。

这些要求意味着:

  • stack1 是一个 T1 类型的容器
  • stack2 是一个 T2 类型的容器;
  • stack1stack2 中的元素类型相同

类型擦除

前面我们已经了解了什么是协议,什么是泛型。接下来我们了解一下什么是类型擦除。首先我们先来看一个例子(这里我们定义了一个泛型协议)

protocol DataFetch { 
    associatedtype dataType
    func fetch(completion: ((Result<DataType, Error>) -> Void)?) 
}

紧接着我们需要请求一个用户数据

struct User {
    let userId: Int
    let name: String 
}

struct UserData: DataFetch { 
    typealias dataType = User
    func fetch(completion: ((Result<FetchType, Error>) -> Void)?)  { 
        let user = User(userId: 1001, name: "Kody") 
        completion?(.success(user))
    } 
}

这时候我们需要使用我们当前的 UserData 获取我们当前的 User 数据

class someViewController { 
    let userData: DataFetch
}

运行一下,会发现一个报错信息

也就意味着 DataFetch 只能用作泛型约束,不能用作具体类型!因为编译器无法确定 dataType 的具体类型是什么!那有同学就会说了,这还不简单我直接改成下面这样就行了。

但是这会在 ViewControllerUserData 对象之间创建一个依赖关系。如果我们遵循 SOLID 原则,我们希望避免依赖并隐藏实现细节。这个时候怎么办?我们就需要引入一个中间层来解决 这样的问题。

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<T, Error>) -> Void)?) { 
        _fetch(completion)
    } 
}
  • 这里我们定义了一个中间层结构体 AnyDataFetchAnyDataFetch 实现了 DataFetch 的所 有方法。
  • AnyDataFetch 的初始化过程中,实现协议的类型会被当做参数传入(依赖注入)
  • AnyDataFetch 实现的具体协议方法 fetch 中,再转发实现协议的抽象类型。

这个时候我们就可以把 AnyDataFetch 当做具体类型使用。

class someViewController {
    let userData: AnyDataFetch<User>
    init(userData: AnyDataFetch<User>) { 
        self.userData = userData
    } 
}

let userData = UserData()
let anyDataFetch = AnyDataFetch<User>(userData) 
vc.userData.fetch { (result) in
    switch result {
    case .success(let user):
        print(user.name) 
    case .failure(let error):
        print(error) 
    }
}

这里可能大家看不出有什么区别,这样做的好处就是我们不用知道当前请求的具体类型是什么, 也就意味着如果我改变了之后

struct VIPFetch: DataFetch { 
    typealias DataType = User
    
    func fetch(completion: ((Result<DataType, Error>) -> Void)?) { 
        let user = User(id: 0001, name: "VIP") 
        completion?(.success(user))
    } 
}

let vipFetch = VIPFetch()
let anyDataFetch = AnyFetchable<User>(vipFetch)
let vc = someViewController.init(userData: anyDataFetch)

someDaveStruct.userFetch.fetch { (result) in
    switch result {
    case .success(let user):
        print(user.name) 
    case .failure(let error):
        print(error) 
    }
}

对于 someViewController, 我接收的其实还是 AnyFetchable<User> 类型,这其实就是所谓的类 型擦除,系统中的 AnySequenceAnyCollection 都是这样的原理。

AnySequence 使用案例补充

假设你有这样一个需求,你需要迭代你的自定义属性 User,其中 User 属性如下

struct User {
    var userId: Int
    var name: String 
}

此刻对于 User 来说,我们应该要做的是实现 Sequence 协议

struct User: Sequence { 
    var userId: Int
    var name: String
    func makeIterator() -> CustomIterator { 
        return CustomIterator(obj: self)
    } 
}

struct CustomIterator: IteratorProtocol { 
    var children: Mirror.Children

    init(obj: Persion) {
        children = Mirror(reflecting: obj).children
    }

    mutating func next() -> String? {
        guard let child = children.popFirst() else { return nil } 
        return "\(child.label.wrapped) is \(child.value)"
    } 
}

那这个时候,对于我们当前的另一个⻚面的 VIP 用户数据,也需要遍历,其中 VIP 的数据类型如下。

struct VIP {
    var vipdate: String 
    var viplevel: Int
    var vipName: String
}

对于 VIPUSer 来说他们的行为是一致的,所以这里我们希望抽象出一个统一的协议

struct CustomDataIterator: IteratorProtocol { 
    var children: Mirror.Children
    init(obj: Any) {
        children = Mirror(reflecting: obj).children
    }
    mutating func next() -> String? {
        guard let child = children.popFirst() else { return nil } 
        return "\(child.label.wrapped) is \(child.value)"
    } 
}

protocol CustomDataSeuqence: Sequence { } 
extension CustomDataSeuqence {
    func makeIterator() -> CustomDataIterator { 
        return CustomDataIterator(obj: self)
    } 
}

此刻对于我们的 VIP 来说,只需要这样做

struct VIP: CustomDataSeuqence { 
    var vipdate: String = "20221230" 
    var viplevel: Int = 10
    var vipName: String = "lg"
}

接下来比如我们要做一个社群功能,需要当前同一个社群当中的用户进行遍历,如果是 VIP,需 要特殊显示一些效果

for obj in Users {
    for userProperty in obj {
        print(userProperty) 
    }
}

那么这里的 Users 应该定义成什么类型呢?定义成 Any 类型,这个时候对于当前页面来说就需要将 Any 的类型转成 CustomDataSeuqence,这样虽然可以,但是有点牵强,因为我们已经明确知道当前的类型

let users: [CustomDataSeuqence] 

如果是这样传值,编译器肯定会报错,因为当前编译器无法确定类型,这个时候我们就可以使用 AnySeuqence

let user = User() 
let vip = VIP()

let users: [AnySequence<String>] = [AnySequence(user), AnySequence(vip)] 

for user in users {
    // print(user)
    for item in user { 
        print(item)
    } 
}

这个时候 AnySequence 就将具体的 Sequence 类型隐藏了,调用者只知道数组中的元素是一个可以迭代输出字符串类型的序列。

Sequence & Collection

Sequence 协议来说,表达的是既可以是一个有限的集合,也可以是一个无限的集合,而它只需 要提供集合中的元素,和如何访问这些元素的接口即可。

image.png

Iterators

在我们研究 Sequence 之前,我们先从我们日常比较常⻅的一段代码入手:

let numbers = [2, 4, 6, 8]

for num in numbers { 
    print(num)
}

这就是一个非常常⻅的 for 循环,但是大家有没有思考过,这里是如何进行 for 循环的遍历的?这里我们直接编译一下源码,借助 SIL 来看一下代码的执行流程

image.png

这里的代码我们就不用分析基本上一看就是在分配内存空间,然后把字面量的值存入到 Array 当中

image.png

创建了一个 name$num$generatorIndexingIterator

image.png

可以看到这里调用了一个 makeIterator() 的方法,这个方法需要两个参数一个是在上文中创建的 IndexingIterator, 一个是 Array 的引用。我们接着往下看

image.png

这里调用 IndexingIteratornext() 方法来遍历数组中一个又一个的元素。

接下来我们定位到 Collection.swift 这个文件,我们看一下:

image.png

我们来找到这个文件,是一个一次提供一个序列值的类型, 它和协议是息息相关的,每次通过创建迭代器来访问序列中的元素。

所以我们每次在使用 for..in 的时候,其实都是使用这个集合的迭代器来遍历当前集合或者序列当中的元素。

我们来看一下 IteratorProtocol 的定义:

image.png

当前协议有一个关联类型 Element,其实有一个 mutating 的方法 nextnext 方法返回一 个 element

接下来就是 Sequence 协议的定义:

image.png

对于 Sequence 协议来说,表达的是既可以是一个有限的集合,也可以是一个无限的集合,而它 只需要提供集合中的元素,和如何访问这些元素的接口即可。

我们来自定义的试一下:假设我们要用一个结构体来模拟一个集合,对于一个给定的初始值,那么当前集合中包含从 0...count 的整形集合。

image.png

同样的我们也可以创建一个无限的序列,下面这个代码就会打印无穷的数字(除非你在开始给定的就是 nil

struct unlimitedIterator: IteratorProtocol { 
    let value: Int
    func next() -> Int? {
        return value 
    }
}
var iterator = unlimitedIterator(value: 10) 
while let x = iterator.next() {
    print(x) 
}

Collection协议

定义 startIndexendIndex 属性,表示集合起始和结束位置; 定义一个只读的下标操作符; 实现一个 index(after:) 方法用于在集合中移动索引位置;

MutableCollection: 允许集合通过下标修改自身元素 MutableCollection 支持集合通过下标的方式改变自身的元素,比如上个案例当中我们实 现了 subscriptgetset,对于 Collection 来说可以不提供 set,这个时候我们就没有办法通过下标的方式改变自身元素了,所以对 MutableColletion 来说下标语法提供一个 setter 就行

RangeReplaceColletion: 允许集合修改任意区间的元素 那这里如果我们想 Remove 掉一个元素是不是需要自己实现 Remove 方法呢?这些在 Swift 标准库中有专⻔的协议约定了,这个协议叫做 RangeReplaceColletion

这个协议允许我们通过一个集合来替换当前集合当中任意自己的元素,同时支持我们删除和插入 元素的操作。

当我们自定义集合要遵循 RangeReplaceableCollection 的时候,我们需要提供一个默认的 init 方法,以及 replaceSubrange(:with:) 方法。其他的如果不需要 RangeReplaceableCollection 都有默认的实现。

BidirectionalCollection:可以向前或向后遍历集合

比如可以获取最后一个元素、反转序列、快速的获取倒序等等。既然正序是通过 subscriptindex(after:) 来实现的,那么倒序添加一个 index(before:) 就可以往前递归了,这就好像双向链表一样,只不过双向链表获取的是值,而这里的集合获取的都是索引。

RandomAccessCollection:任意访问集合元

实现 index(:offsetBy:)distance(from:to:)

分类:
iOS
标签:
分类:
iOS
标签:
收藏成功!
已添加到「」, 点击更改