「这是我参与2022首次更文挑战的第23天,活动详情查看:2022首次更文挑战」。
我们之前知道函数的本质是引用类型,我们看下函数添加泛型参数呢?
1. 普通泛型参数
我们看下之前的泛型参数的函数
func testGenric<T>(_ value: T) -> T{
let tmp = value
return tmp
}
我们编译器不知道T时什么类型,这个时候我们赋值,这个T可能是个值类型,也可能是个引用类型,或者实例类型。我们的编译器不知道要分配多大的内存空间,它的步长是多少。
我们先把它编译成sil代码,编译
可以发现并没有关于内存的相关代码,继续往下通过编译,我们生成中间代码IR,我们把当前文件编译成main.ll文件
看下testGenric函数的实现
通过valueWitnesses进行管理的,我们看下它的结构
可以看到有好多个 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
我们修改下
对于泛型它的大小记录在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代码
正常情况我们对于闭包是{ 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
当我们传入的泛型参数是函数的时候,会把它再次封装一层,捕获传入的闭包,便于统一管理。另外对于它是如何分辨当前传入的是闭包还是函数呢,是通过 metadata 来进行判断的。