Swift基础iOS 开发每天分享优质文章HTML5开发

Swift进阶-泛型

2022-03-17  本文已影响0人  顶级蜗牛

Swift进阶-类与结构体
Swift-函数派发
Swift进阶-属性
Swift进阶-指针
Swift进阶-内存管理
Swift进阶-TargetClassMetadata和TargetStructMetadata数据结构源码分析
Swift进阶-Mirror解析
Swift进阶-闭包
Swift进阶-协议
Swift进阶-泛型
Swift进阶-String源码解析
Swift进阶-Array源码解析

为什么会有泛型?

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

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

我们发现, multiNumInt(::) 、 multiNumDouble(::) 函数体是一样的。唯一的区别是它们接收值类型不同( Int 、 Double )。
这个时候我们想找到一个可以计算任意类型值的函数怎么办?泛型正是能让我们写出这样函数的语法。

一、泛型语法

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

func multiNum<T: FloatingPoint>(_ x: T, _ y: T) -> T { 
    return x * y 
}
struct Stack {
    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 Stack<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 Stack: 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 拥有两个约束:
1.它必须遵循 EvenProtocol协议(就是当前定义的协议);
2.以及它的 Item 类型必须是和容器里的 Item 类型相同。
Item 的约束是一个 where 分句。

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
}


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

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

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

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

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

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

下面是函数的两个类型形式参数上设置的要求:
1、 T1 必须遵循 StackProtocol 协议(写作 T1: StackProtocol );
2、 T2 也必须遵循 StackProtocol 协议(写作 C2: StackProtocol );
3、 T1 的 Item 必须和 T2 的 Item 相同(写作 T1.Item == C2.Item);
4、 T1 的 Item 必须遵循 Equatable 协议(写作 T1.ItemType: Equatable )。

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

这些要求意味着:
1、 stack1 是一个 T1 类型的容器;
2、 stack2 是一个 T2 类型的容器;
3、 stack1 和 stack2 中的元素类型相同

二、类型擦除

给一个类型耦合的例子:

// 定义数据拉取协议
protocol DataFetch {
    associatedtype DataType

    func fetch(completion: ((Result<DataType, Error>)->Void))
}


// 数据模型
struct User {
    let userId: Int
    let name: String
}

// 定义数据拉取工具类 - 并且确定拉取后通过闭包返回User模型
struct UserDataFetch: DataFetch {
    
    typealias DataType = User
    
    // 类似网路网络请求,获取数据
    func fetch(completion: ((Result<DataType, Error>) -> Void)) {
        let user = User(userId: 1001, name: "安安")
        completion(.success(user))
    }
}

接下来看看ViewController会耦合的地方:

class MyViewController {
    let dataFetch: UserDataFetch
    
    init(_ dataFetch: UserDataFetch) {
        self.dataFetch = dataFetch
    }
}

可以看到ViewController里面声明了一个UserDataFetch实例,那就会出现一种情况是那如果我又有一个VipUserDataFetch类型呢,那就又得嵌套进了ViewController了,那就会显得臃肿且耦合,那如果我们声明成DataFetch协议类型呢?

因为我们声明是一个泛型协议,会报错的:

想要解决上面的耦合性,又想VC传入的时候使用泛型协议且又想绕过编译器的检查,可以抽象出一层中间层AnyDataFetch通过依赖注入的方式把协议的具体类型传递进来了,并让其做一个泛型约束:

// 定义一个中间层处理vc数据接收,因为vc接收的不一定只有 UserData 获取的数据,也可以VipUserData等等...
struct AnyDataFetch<T>: DataFetch {
    typealias DataType = T
    
    private let _fetch: ((Result<T, Error>) -> Void) -> Void
    
    init<U: DataFetch>(_ fetchable: U) where U.DataType == T {
        self._fetch = fetchable.fetch
    }
    
    func fetch(completion: ((Result<T, Error>) -> Void)) {
        _fetch(completion)
    }
}

class MyViewController {
    let dataFetch: AnyDataFetch<User>

    init(_ dataFetch: AnyDataFetch<User>) {
        self.dataFetch = dataFetch
    }
}


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

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

struct VipDataFetch: DataFetch {

    typealias DataType = User

    func fetch(completion: ((Result<DataType, Error>) -> Void)) {
        let user = User(userId: 0001, name: "VIP")
        completion(.success(user))
    }
}

let vipDataFetch = VipDataFetch()
let anyDataFetch1 = AnyDataFetch<User>(vipDataFetch)
let vc = MyViewController(anyDataFetch1)
vc.dataFetch.fetch { (result) in
    switch result {
        case .success(let user):
        print(user.name)
        case .failure(let error):
        print(error)
    }
}

对于VC来说压根儿不关心具体的DataFetch类型了,关注点只放在model上。
系统上对于类型擦除的运用比如:AnyCollection、AnySequence

AnySequence使用案例

假设你有这样一个需求,你需要迭代你的自定义属性 User ;此刻对于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: Any) {
        children = Mirror(reflecting: obj).children
    }
    
    mutating func next() -> String? {
        guard let child = children.popFirst() else { return nil }
        return "\(child.label ?? "无label") is \(child.value)"
    }
}

那这个时候,对于我们当前的另一个页面的 VIP 用户数据,也需要遍历,对于 VIPUSer 来说他们的行为是一致的,所以这里我们希望抽象出一个统一的协议:

struct VIP: CustomDataSeuqence{
    var vipdate: String = "20221226"
    var viplevel: Int = 18
    var vipName: String = "林林"
}


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

接下来比如我们要做一个社群功能,需要当前同一个社群当中的用户进行遍历,如果是VIP,需要特殊显示一些效果。
那么这里的 Users 应该定义成什么类型呢?定义成 Any 类型,这个时候对于当前页面来说就需要讲 Any 的类型强转成 CustomDataSeuqence ,可以,但是有点牵强。这个时候我们就可以使用 AnySeuqence

let user = User(userId: 1001, name: "安安")
let vip = VIP()

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

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

打印结果:

userId is 1001
name is 安安
vipdate is 20221226
viplevel is 18
vipName is 林林

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

三、泛型的底层原理

func testGenric<T>(_ value: T) -> T {
    let tmp = value
    return value
}

testGenric(10)

对于编译器来说,它不知道T到底是什么具体的类型(可能是值类型,也可能是引用类型),那编译器就不知道 tmp / value 到底分配多少内存空间、步长是多少、对齐的字段是多少...只有当真正传递参数才知道真实类型对吧。
tmp在方法栈占据内存空间编译器是怎么决断的呢?

来看看上面代码编译成IR代码进行分析:

main testGenric

可以看到在调用testGenric时候可以看到一个值见证表valueWitnesses,并且可以看出到initializeWithCopy、destroy等函数都是从值见证表来的,所以泛型是通过ValueWitnessTable去管理内存的。

ValueWitnessTable结构体

截图是ValueWitnessTable的数据结构,前面几个i8*其实就是void *代表内存管理函数,剩下的就是size、stride、flags、extraInhabitantCount,把数据结构还原一下

ValueWitnessTable的数据结构:

// 弱引用表
struct ValueWitnessTable {
    var unknow1: UnsafeRawPointer
    var unknow2: UnsafeRawPointer
    var unknow3: UnsafeRawPointer
    var unknow4: UnsafeRawPointer
    var unknow5: UnsafeRawPointer
    var unknow6: UnsafeRawPointer
    var unknow7: UnsafeRawPointer
    var unknow8: UnsafeRawPointer
    var size: Int
    var stride: Int
    var flags: UInt32
    var extraInhabitantCount: UInt32
}

不管testGenric函数的参数传递的是值/引用类型,不管什么类型,总之它的metadata一定有ValueWitnessTable!并且保存着这个类型的size、stride、flags、extraInhabitantCount还有一些内存管理函数等信息。

尝试还原一下上面分析的结构体对不对,举一个案例呗:

struct Teacher {
    var age = 10
}

// 所有类型的最终基类 Metadata
struct TargetMetadata {
    var kind: Int // kind用于区分类型的
}

ps: 不懂为啥是TargetMetadata可以看我之前f的分享的文章

let ptr = unsafeBitCast(Teacher.self as Any.Type, to: UnsafeMutablePointer<TargetMetadata>.self)
let valueWitnessTable = UnsafeRawPointer(ptr).advanced(by: -MemoryLayout<UnsafeRawPointer>.size).assumingMemoryBound(to: UnsafeMutablePointer<ValueWitnessTable>.self).pointee
print(valueWitnessTable.pointee.size) // 8 
// 如果给 Teacher 添加一个属性 var age1 = 20,则打印的size就是 16


print(valueWitnessTable.pointee.unknow1) // 0x00007ff81b935400
print(valueWitnessTable.pointee.unknow2) // 0x00007ff81b935410
print(valueWitnessTable.pointee.unknow3) // 0x00007ff81b935420
print(valueWitnessTable.pointee.unknow4)
print(valueWitnessTable.pointee.unknow5)
print(valueWitnessTable.pointee.unknow6)
print(valueWitnessTable.pointee.unknow7)
print(valueWitnessTable.pointee.unknow8)
print("end")

ps: 为什么ValueWitnessTable是在TargetMetadata向前移动?

在IR代码中就能分析出,ValueWitnessTable的位置是在TargetMetadata的向前移动-1的位置。

来看看unknow1打印的是什么?

image.png

得到函数名,可以自行打印最后恢复ValueWitnessTable的数据结构:

// 弱引用表
struct ValueWitnessTable {
    var initializeBufferWithCopyOfBuffer: UnsafeRawPointer
    var destroy: UnsafeRawPointer
    var initializeWithCopy: UnsafeRawPointer
    var assignWithCopy: UnsafeRawPointer
    var initializeWithTake: UnsafeRawPointer
    var assignWithTake: UnsafeRawPointer
    var getEnumTagSinglePayload: UnsafeRawPointer
    var storeEnumTagSinglePayload: UnsafeRawPointer
    var size: Int
    var stride: Int
    var flags: UInt32
    var extraInhabitantCount: UInt32
}

在来对比刚才testGenric 编译后的IR代码里的,如果我们上面的结论:不管什么类型它的metadata一定有ValueWitnessTable是对的,那一定可以在IR上找到上面结构体里还原出来的函数。

恰好能对应上面还原的方法名:

总结:

如果传递的参数是泛型呢?会发生什么变化?

来看看下面这个案例

func makeIncrementer() -> (Int) -> Int {
    var runningTotal = 10
    func incrementer(mount: Int) -> Int {
        runningTotal += mount
        return runningTotal
    }
    return incrementer
}

func genric<T>(t: T) {    
}

let increment = makeIncrementer()
genric(t: increment)

编译成IR代码,找到mian函数:

define i32 @main(i32 %0, i8** %1) #0 {
entry:
  %2 = alloca %swift.function, align 8
  %3 = bitcast i8** %1 to i8*

  // 调用makeIncrementer,生成闭包
  %4 = call swiftcc { i8*, %swift.refcounted* } @"$s4main15makeIncrementerS2icyF"()
  %5 = extractvalue { i8*, %swift.refcounted* } %4, 0
  %6 = extractvalue { i8*, %swift.refcounted* } %4, 1

  // 将指针存储到increment变量里
  store i8* %5, i8** getelementptr inbounds (%swift.function, %swift.function* @"$s4main9incrementyS2icvp", i32 0, i32 0), align 8
  // 将捕获的变量存储到increment变量里
  store %swift.refcounted* %6, %swift.refcounted** getelementptr inbounds (%swift.function, %swift.function* @"$s4main9incrementyS2icvp", i32 0, i32 1), align 8

  // 把function类型转换成void *
  %7 = bitcast %swift.function* %2 to i8*
  call void @llvm.lifetime.start.p0i8(i64 16, i8* %7)
  // 从increment变量取出指针和捕获的变量
  %8 = load i8*, i8** getelementptr inbounds (%swift.function, %swift.function* @"$s4main9incrementyS2icvp", i32 0, i32 0), align 8
  %9 = load %swift.refcounted*, %swift.refcounted** getelementptr inbounds (%swift.function, %swift.function* @"$s4main9incrementyS2icvp", i32 0, i32 1), align 8

  // 一些内存管理的函数
  %10 = call %swift.refcounted* @swift_retain(%swift.refcounted* returned %9) #3

  // swift_allocObject创建了堆区的内存空间
  %11 = call noalias %swift.refcounted* @swift_allocObject(%swift.type* getelementptr inbounds (%swift.full_boxmetadata, %swift.full_boxmetadata* @metadata, i32 0, i32 2), i64 32, i64 7) #3
  %12 = bitcast %swift.refcounted* %11 to <{ %swift.refcounted, %swift.function }>*

  // 取出第一个成员是function这个结构体
  %13 = getelementptr inbounds <{ %swift.refcounted, %swift.function }>, <{ %swift.refcounted, %swift.function }>* %12, i32 0, i32 1
  // 从function这个结构体取出函数地址
  %.fn = getelementptr inbounds %swift.function, %swift.function* %13, i32 0, i32 0
  store i8* %8, i8** %.fn, align 8
  %.data = getelementptr inbounds %swift.function, %swift.function* %13, i32 0, i32 1
  store %swift.refcounted* %9, %swift.refcounted** %.data, align 8
  %.fn1 = getelementptr inbounds %swift.function, %swift.function* %2, i32 0, i32 0
  store i8* bitcast (void (%TSi*, %TSi*, %swift.refcounted*)* @"$sS2iIegyd_S2iIegnr_TRTA" to i8*), i8** %.fn1, align 8

  %.data2 = getelementptr inbounds %swift.function, %swift.function* %2, i32 0, i32 1
  store %swift.refcounted* %11, %swift.refcounted** %.data2, align 8
  %14 = bitcast %swift.function* %2 to %swift.opaque*
  // 后面又对这个function又做了一层重新的抽象
  %15 = call %swift.type* @__swift_instantiateConcreteTypeFromMangledName({ i32, i32 }* @"$sS2icMD") #10
  call swiftcc void @"$s4main6genric1tyx_tlF"(%swift.opaque* noalias nocapture %14, %swift.type* %15)
  %.data3 = getelementptr inbounds %swift.function, %swift.function* %2, i32 0, i32 1
  %16 = load %swift.refcounted*, %swift.refcounted** %.data3, align 8
  call void @swift_release(%swift.refcounted* %16) #3
  %17 = bitcast %swift.function* %2 to i8*
  call void @llvm.lifetime.end.p0i8(i64 16, i8* %17)
  ret i32 0
}

可以看到func genric<T>(t: T) 当泛型参数接收一个闭包/函数时,会对这个闭包/函数原有数据结构上又抽象封装了一层。因为我们传入可能是是函数,也可能传入的是闭包表达式,编译器不知道呀,它就和协议设计一样不知道谁遵循了协议,所以它就抽象了一层中间层为了泛型管理统一区分类型。

当去调用的时候,是通过这个抽象出来的中间层来取调用,如果不调用而用来传值的话,依旧是通过上面的ValueWitnessTable进行内存管理的。(注意:函数/闭包也是引用类型)。

func genric<T>(t: T) {    
    // VWT管理内存
    let tmp = t
    // 函数调用,抽象中间层统一管理函数/闭包
    var t1 = t as! (Int) -> Int
    t1()
}

原本对于闭包的数据结构是这样的:(想了解可点击 函数/闭包数据结构)

// 闭包的实质
struct ClosureData<T> {
    var ptr: UnsafeRawPointer // 闭包地址
    var box: UnsafePointer<T> // Box
}

// 捕获单个外部变量的时候Box
struct Box<T> {
    var heapObject: HeapObject // 实例对象的内存地址
    var value: T
}

// 捕获多个外部变量的时候Box
//struct Box<T1, T2> {
//    var object: HeapObject  // 把value1、value2统一当成object的属性
//    var value1: UnsafePointer<T1>
//    var value2: T2
//    var value3.......
//}

struct HeapObject {
    var metadata: UnsafeRawPointer
    var refcount1: UInt32
    var refcount2: UInt32
}

泛型参数接收闭包/函数后对其进行调用,又抽象一层封装的数据结构:

// 闭包作为泛型参数,又包装了一层
struct ReabstractionThunkContext<Context> {
    var heapObject: HeapObject
    var function: ClosureData<Context>
}

验证数据结构:

func makeIncrementer() -> (Int) -> Int {
    var runningTotal = 10
    func incrementer(mount: Int) -> Int {
        runningTotal += mount
        return runningTotal
    }
    return incrementer
}

func genric<T>(t: T) {
    let ptr = UnsafeMutablePointer<T>.allocate(capacity: 1)
    ptr.initialize(to: t)
    
    defer {
        ptr.deinitialize(count: 1)
        ptr.deallocate()
    }
    
    let closure_ptr = ptr.withMemoryRebound(to: ClosureData<ReabstractionThunkContext<Box<Int>>>.self, capacity: 1) { $0 }
    let ctx = closure_ptr.pointee.box.pointee.function.box
    print(ctx.pointee.value) // 10    得到捕获的外部变量的值
}

let increment = makeIncrementer()
genric(t: increment)
上一篇下一篇

猜你喜欢

热点阅读