Swfit进阶-20-Swift中的泛型的内存结构

214 阅读2分钟

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

我们之前知道函数的本质是引用类型,我们看下函数添加泛型参数呢?

1. 普通泛型参数

我们看下之前的泛型参数的函数

func testGenric<T>(_ value: T) -> T{

    let tmp = value

    return tmp

}

我们编译器不知道T时什么类型,这个时候我们赋值,这个T可能是个类型,也可能是个引用类型,或者实例类型。我们的编译器不知道要分配多大的内存空间,它的步长是多少。

我们先把它编译成sil代码,编译

image.png

可以发现并没有关于内存的相关代码,继续往下通过编译,我们生成中间代码IR,我们把当前文件编译成main.ll文件

image.png

看下testGenric函数的实现

image.png

通过valueWitnesses进行管理的,我们看下它的结构

image.png

可以看到有好多个 i8*,那这种 i8* 我们都可以把它当作 void*,也就是这些 i8* 其实就是当前所谓的函数。此时根据这个结构我们把 ValueWitnessTable 的结构还原出来。 因此我们定义下它的结构体

struct ValueWitnessesTable {

    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: Int

}

我们打印下我们定义的结构体的范型大小

struct Person{

    

    var age = 18

}


struct TargetMetadata{

    var kind:Int

}


var structType = Person.self


let ptr = unsafeBitCast(structType as Any.Type, to:UnsafeMutablePointer<TargetMetadata>.self)

let valueWitnessTable = UnsafeRawPointer(ptr).advanced(by: -MemoryLayout<UnsafeRawPointer>.size).assumingMemoryBound(to: UnsafeMutablePointer<ValueWitnessesTable>.self).pointee

print(valueWitnessTable.pointee.size)

此时大小就是8 image.png

我们修改下

image.png

对于泛型它的大小记录在valueWitnessTable中。我们通过源码可以还原出这个valueWitnessTable的结构。

struct ValueWitnessTable {
    var initializeBufferWithCopy: 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
}

对于值类型来说,copy move 进行内存拷贝通过这个ValueWitnessTable进行保存。

我们对于引用类型来说:拷贝指针,引用计数+1.

2. 闭包作为泛型参数

func makeIntcrement() ->(Int)->Int{

    

    var runingTotal = 10

    return{

        

        runingTotal += $0

        return runingTotal

    }

 
    

}

func test<T>(t:T){


}
let f = makeIntcrement()

test(t: f)

我们把它编译成IR代码

image.png

正常情况我们对于闭包是{ i8*, %swift.refcounted* }类型,对于是泛型的话则又生成了一个中间层,来存储我们的闭包。它的结构为{i8*, {%swift.refcounted, {i8*, i64}}}

我们根据之前对闭包的研究还原它的结构

struct HeapObject {
    var matedata: UnsafeRawPointer
    var refcount: Int
}

struct FuntionData<T> {
    /// 函数地址
    var ptr: UnsafeRawPointer
    /// 存储捕获堆空间地址的值
    var object: UnsafePointer<T>?
}

// 中间层
struct ReabstractionThunkContext<Context> {
    var heapObject: HeapObject
    var function: FuntionData<Context>
}

struct Box<T>{
    var object: HeapObject
    var value: T
}

我们验证下捕获的值

func test<T>(t:T){

    let ptr = UnsafeMutablePointer<T>.allocate(capacity: 1)

      ptr.initialize(to: t)
      
      let ctx = ptr.withMemoryRebound(to: FuntionData<ReabstractionThunkContext<Box<Int>>>.self, capacity: 1) {

          $0.pointee.object?.pointee.function.object

      }

    print(ctx?.pointee.value ?? 0)
    

}

let f = makeIntcrement()

test(t: f)

打印为10 image.png

当我们传入的泛型参数是函数的时候,会把它再次封装一层,捕获传入的闭包,便于统一管理。另外对于它是如何分辨当前传入的是闭包还是函数呢,是通过 metadata 来进行判断的。