iOS-Swift 独孤九剑:九、泛型与类型擦除

988 阅读19分钟

一、泛型的基础语法

1. 初识泛型

泛型可以将类型参数化,提高代码复用效率,减少代码量。我们来看一个例子,交换两个变量的值,代码如下:

var v1 = 10
var v2 = 20

func swapValues(_ a: inout Int, _ b: inout Int) {
    (a, b) = (b, a)
}

print("交换前 v1: \(v1), v2: \(v2)")
swapValues(&v1, &v2)
print("交换后 v1: \(v1), v2: \(v2)")
打印结果:
交换前 v1: 10, v2: 20
交换后 v1: 20, v2: 10

可以看到,我们通过 swapValues 函数交换了 v1 和 v2 的值,但是这个函数有一个局限性,就是只能接收 Int 类型的参数。那我们想要接收其它类型的参数,但又不想写很多个 swapValues 函数,就可以使用泛型去解决,代码如下:

var v1 = 10.0
var v2 = 20.0

func swapValues<T>(_ a: inout T, _ b: inout T) {
    (a, b) = (b, a)
}

print("交换前 v1: \(v1), v2: \(v2)")
swapValues(&v1, &v2)
print("交换后 v1: \(v1), v2: \(v2)")
打印结果:
交换前 v1: 10.0, v2: 20.0
交换后 v1: 20.0, v2: 10.0

注意看,我们在方法名称的后面加上 <T>,代表我们指定了一个占位符 T,这个 T 代表任意类型。此时可以把原来 Int 类型的参数换成 T,这个 T 就是泛型。通过打印结果也成功的交换了两个变量的值。

那如果想要 a 和 b 的参数不一致,以上的这种写法是不能满足的,我们可以把 swapValues 函数改成如下代码:

func swapValues<T1, T2>(_ a: inout T1, _ b: inout T2) {
    (a, b) = (b, a)
}

这样就可以实现一个函数传多个个不同类型的参数了,当然,(a, b) = (b, a) 这句代码肯定是会报错的,因为类型不一样,肯定不能做值的交换了。

接下来还有一种就是把泛型函数赋值给变量/常量,代码如下:

let fn: (inout Double, inout Double) -> () = swapValues
print("交换前 v1: \(v1), v2: \(v2)")
fn(&v1, &v2)
print("交换后 v1: \(v1), v2: \(v2)")

在把函数类型赋值给 fn 的时候,需要明确在变量/常量名后面明确这个泛型的类型,也就是明确这个函数参数的类型。

2. 泛型类型

平时写的结构体和类也是可以增加泛型的,这种类型称之为泛型类型。代码如下:

class Stack<E> {
    var elements = [E]()
    func push(_ element:E) { elements.append(element) }
    func pop() -> E{ elements.removeLast() }
    func top() -> E{ elements.last! }
    func size() -> Int { elements.count }
}

我们定义一个类 - Stack,并且给它增加一个泛型 E,那这个 Stack 有 push,pop,top,size 等方法。这些方法也就是平时说的入栈、出栈等操作。此时这个泛型类型就可以接收任何类型的元素。

比如说,我们要往这个栈里存储 Int 类型的元素,我们要指定这个泛型的具体类型为 Int 类型,代码如下:

var stack = Stack<Int>()
stack.push(10)
stack.push(20)
stack.push(30)
print(stack.top())  //30
print(stack.pop())  //30
print(stack.pop())  //20
print(stack.pop())  //10
print(stack.size()) //0

我们还可以给这个泛型类型指定一个默认的初始化器,代码如下:

init(firstElement: E) { elements.append(firstElement) }

使用的时候:

var stack = Stack(firstElement: 10)

注意看这个初始化器,有一个参数 firstElement,那我们通过这个初始化方法的时候,就可以不用指定这个泛型的具体类型。比如传 10,此时编译器在编译的时候就已经知道 10 是 Int 类型的,那也就意味这个泛型的具体类型也是 Int 类型的。

如果这个泛型类型存在继承的话,它的子类也要加上泛型,代码如下:

class SubStack<E>: Stack<E> {}

3. 关联类型

既然泛型这么方便,我们可不可以把泛型用在协议上呢。比如 Stack<E>,在第 2 点举的例子中 Stack<E> 是一个类,此时希望它可以是一个类,也可以是一个结构体或者是其他的类型,也就意味着,我们可以把相同的方法抽取出来,用协议来约束 Stack<E> 的方法,具体的实现由遵守协议的不同类型自行处理。那我们就需要用到一个叫做关联类型的东西。

associatedtype 关键字可以声明一个关联类型,关联类型的作用是给协议中用到的类型定义一个占位名称,并且协议中可以拥有多个关联类型。代码如下:

protocol Stackable {
    associatedtype Element //关联类型
    mutating func push(_ element: Element)
    mutating func pop() -> Element
    func top() -> Element
    func size() -> Int
}

注意看,push 方法和 pop 方法在前面用 mutating 修饰,这是为了给值类型也可以遵守这份协议,因为这两个方法都是要修改 Stack<E> 的内存。接下来我们可以用一个泛型类型来遵守 Stackable,代码如下:

class Stack<E>: Stackable {
    typealias Element = E
    var elements = [E]()
    func push(_ element: E) { elements.append(element) }
    func pop() -> E{ elements.removeLast() }
    func top() -> E{ elements.last! }
    func size() -> Int { elements.count }
}

此时我们通过 typealias 给关联类型设定了一个类型,这个类型就是泛型 E,但是其实这一句代码是可以省略的。代码如下:

class Stack<E>: Stackable {
    // typealias Element = E
    var elements = [E]()
    func push(_ element: E) { elements.append(element) }
    func pop() -> E{ elements.removeLast() }
    func top() -> E{ elements.last! }
    func size() -> Int { elements.count }
}

4. 类型约束

什么叫类型约束呢,我们还是以 swapValues 函数为例,代码如下:

protocol Runnable {}
class Person {}

func swapValues<T: Person & Runnable>(_ a: inout T,_ b: inout T) {
    (a,b)=(b,a)
}

就如同上面的代码,这么写的意思是对泛型 T 进行了一个约束:此时这个 T 不是什么类型都可以,这个 T 必须是遵守了 Runnable 协议并且是 Person 或者是 Person 类型的的子类。

我们再来看一个例子,代码如下:

protocol Stackable {
    associatedtype Element: Equatable
}

class Stack<E: Equatable>: Stackable {
    typealias Element = E
}

协议中的中关联类型 Element 遵守 Equatable 协议,那么遵守这个协议的泛型类型的元素也必须遵守 Equatable 协议,这就对这个泛型的类型进行约束。

我们再来看一个例子,代码如下:

func equal<S1: Stackable, S2: Stackable>(_ s1: S1,_ s2: S2)->Bool
    where S1.Element == S2.Element, S1.Element: Hashable
{
    ......
}

这个函数有两个泛型参数,并且两个泛型参数都要遵守 Stackable 协议。那我们注意看 where 后面的代码:

  • 第一:这里要求 S1 的关联类型必须和 S2 的关联类型相等。

  • 第二:S1 的关联类型必须遵守 Hashable 协议。

总的来说,函数名后面声明的泛型是约束泛型的类型是否遵守 Stackable 协议,而 where 是用来针对 Stackable 协议中的关联类型进行约束的。

类型约束.png

如图所示,s1 和 s2 满足了 equal 函数的所有要求:首先 Stack 遵守 Stackable 协议,其次 s1 和 s2 的关联类型是相等的,都是 Int 类型,并且 Int 类型默认是遵守 Equatable 和 Hashable 协议的。

s1 和 s3 和报错的原因很明显是它们的泛型的关联类型不相等。

二、类型擦除

我在刚开始接触 类型擦除 的时候也是很懵逼,它这个比较抽象,不太容易理解,下面我将通过一些案例来看看 类型擦除 到底是什么。

1. 案例一

首先,这里有一份协议 DataFetch,协议里有一个关联类型 DataType,这份协议用来提取数据用的,所以它声明里一个提取数据的方法 fetch(completion:),代码如下:

// 数据提取器
protocol DataFetch {
    /// 关联类型
    associatedtype DataType

    /// 提取数据的方法
    func fetch(completion: Body<DataType>?)
}

fetch(:) 方法接收一个函数 Body,这个函数的定义如下:

typealias Body<T> = (Result<T, Error>) -> ()

这个函数接收一个 Result 类型的参数,并且它支持是一个泛型参数 T,这个 T 可以是任意类型,因为我们不知道提取出来的数据是字典类型,字符串类型还是数组类型或者是其它类型。

那我为什么要这样做呢?首先这是一份协议,那也就意味着这份协议会被多个类型遵守,因为很可能每个实现 fetch(completion:) 的代码是不一样的,也就是提取数据的方式不一样。

当一个类中包含了一个遵守 DataFetch 协议类型的变量,但这个变量的类型并不是单一的,而希望它支持遵守了 DataFetch 协议的其它类型,因为我们不需要关心数据是如何提取的,只关心提取数据后的结构。此时我们把这个变量当作该类的一个属性或者一个方法中的参数,第一时间想到的是用 DataFetch 作为类型,代码如下:

class SomeViewController {
    let dataFetch: DataFetch

    init(_ dataFetch: DataFetch){
        self.dataFetch = dataFetch
    }
}

此时编译器会报一个错误,如图: 协议类型当作变量类型报错.png

编译器告诉你:协议“DataFetch”只能用作通用约束,因为它具有 Self 或关联的类型要求。也就是当协议中有用到关联类型和 Self 的时候,协议就不能当作类型来使用。

很显然,这种方式在 Swift 中并不允许,因为编译器不知道协议中的这个关联类型的具体类型是什么,就像第 2 点泛型类型中的 Stack<E> 在初始化的时候要指定泛型的类型一样。那要怎么解决呢?这个时候就需要用到一种技术:类型擦除。

类型擦除是一种非常有用的技术,它可用来阻止泛型对代码的侵入,也可用来保证接口简单明了。通过将底层类型包装起来,将API与具体的功能进行拆分。那针对我们的这个需求要怎么做呢,我们一起来看一下。

  • 定义一个中间层类型,该类型实现了协议“DataFetch”中的所有方法。

  • 在中间层类型中实现的具体协议方法里转发给实现协议的抽象类型。

  • 在中间层类型的初始化方法中把抽象类型当参数传入(依赖注入)。

我们先来看这个中间层的实现,代码如下:

struct AnyDataFetch<T>: DataFetch {

    private let _fetch: (Body<T>?)-> ()

    init<U: DataFetch>(_ fetchable: U) where U.DataType == T {
        _fetch = fetchable.fetch
    }

    func fetch(completion: Body<T>?) {
        _fetch(completion)
    }
}

这个中间层我命名为 AnyDataFetch,它遵守 DataFetch 协议,并且实现了协议中的所有方法。这个方法里面就只干一件事,调用协议的抽象类型的 fetch(completion:) 方法。协议的抽象类型的 fetch(completion:) 我们用一个属性去接收。

因为要接收协议的抽象类型的 fetch(completion:) 方法,所以我们要在初始化的时候把协议的抽象类型的实例传进来,然后用 _fetch 去接收。需要注意的是这里要求协议的抽象类型中的 DataType 必须和泛型 T 相同。

此时这个中间层的结构就完成了,接下来是这个抽象类型的定义,我把它命名为 UserData,它用来提取用户的数据,代码如下:

struct UserData: DataFetch {
    func fetch(completion: Body<Model>?) {
        completion?(.success(Model(id: 1001, other: "其他")))
    }
}

协议的抽象类型实现了 fetch(completion:) 方法后,我们在里面简单的写了一个提取成功的回调,这个可以是任意类型,为了方便我就把它写成了一个 Model,Model 的代码如下:

struct Model {
    var id: Int
    var other: Any
}

接下来我们的 SomeViewController 就可以这么去用了,代码如下:

class SomeViewController {
    let dataFetch: AnyDataFetch<Model>

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

    func loadData() {
        self.dataFetch.fetch { body in
            switch body {
            case .success(let model):
                print("success:\(model)" )
            case .failure(let error):
                print("failure:\(error)" )
            }
        }
    }
}

let userData = UserData()
let dataFetch = AnyDataFetch<Model>.init(userData)
let someVC = SomeViewController(dataFetch)
打印结果:
success:Model(id: 1001, other: "其他")

或者我们不实现 loadData 方法,在外面获取数据,代码如下:

someVC.dataFetch.fetch { body in
    switch body {
    case .success(let model):
        print("success:\(model)" )
    case .failure(let error):
        print("failure:\(error)" )
    }
}

上面的例子通过类型擦除技术实现后有两个好处:

  1. 解决了将带有关联类型和 Self 的协议类型不能当作实例的类型的问题。

  2. 在阻止因为泛型对代码的侵入的同时,保证了接口的简单明确,而且提高了代码的复用效率。

也就是当有另一个协议的抽象类型的时候,我们不需要改变 SomeViewController 的代码,不需要改变 AnyDataFetch 的代码。

2. 案例二

这个案例是对 AnySequence 的一个使用,假设你需要迭代你的自定义结构中的属性,这个结构的代码如下:

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

这种需求,我们可以通过实现 Sequence 协议来实现,遵守 Sequence 协议之后我们要实现一个 makeIterator 协议方法,它的返回值是一个 IteratorProtocol 的抽象类型。

那么我们先来实现 IteratorProtocol 的抽象类型,代码如下:

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 ?? "") is \(child.value)"
    }
}

User 的实现代码如下:

struct User: Sequence {
    var userId: Int
    var nickname: String

    func makeIterator() -> some IteratorProtocol {
        return CustomIterator(obj: self)
    }
}

接着我们实现一个打印的方法把 User 的属性信息打印出来:

func printElements(_ obj: User) {
    for e in user {
        print(e)
    }
}

let user = User(userId: 1001, nickname: "Coder_张三")
printElements(user)
userId is 1001
nickname is Coder_张三

此时,我们有同样有另一个结构体也需要实现此功能,比如我定义一个 VIP,代码如下:

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

但是我们的 CustomIterator 都已经写死了,只针对 User 进行打印。但是对于 VIP 和 User 来说他们的行为是一致的,所以我们抽出一个统一的协议,代码如下:

protocol CustomDataSeuqence: Sequence {}

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

那此时对于 VIP 和 User,只需要这样去实现:

struct User: CustomDataSeuqence {
    var userId: Int
    var nickname: String
}

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

我们将 printElements 函数也稍微修改一下,并打印 User 和 VIP:

func printElements<T: Sequence>(_ obj: T) {
    for e in obj {
        print(e)
    }
}

let user = User(userId: 1001, nickname: "Coder_张三")
let vip = VIP(vipdate: "vipdate", viplevel: 5, vipName: "vipName")
printElements(user)
printElements(vip)
打印结果:
userId is 1001
nickname is Coder_张三
vipdate is vipdate
viplevel is 5
vipName is vipName

接下来有一个社区的功能,这个社区需要针对 vip 用户进行一些特殊的现实效果,那么这个数组应该怎么去定义更合适呢,用 [Any] 肯定是不太理想的,因为我们已经明确的知道这个 Any 类型其实就是遵守了 CustomDataSeuqence 的抽象类型,但又不能直接这么写:[CustomDataSeuqence],如果这么写编译器肯定会报错。

这个时候我们可以用 AnySeuqence 来实现,也就是系统定义好的一个遵守 Seuqence 协议的中间层。

接下来呢我需要对这个数组中 User 和 VIP 进行属性的遍历打印,代码如下:

let user = User(userId: 1001, nickname: "Coder_张三")
let vip = VIP(vipdate: "vipdate", viplevel: 5, vipName: "vipName")

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

for obj in users {
    for e in obj {
        print(e)
    }
}
打印结果:
userId is 1001
nickname is Coder_张三
vipdate is vipdate
viplevel is 5
vipName is vipName

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

3. 结束语

Swift 标准库提供了几种可直接利用的类型擦除类型。出来案例二所介绍的 AnySequence 之外,AnyIterator 也是类型擦除的类型,它提供一个类型擦除的迭代器。AnyHashable 也同样是类型擦除的类型,它提供了对Hashable类型访问功能。Swift 还有很多基于集合的擦除类型,你可以通过搜索 Any 来查阅。标准库中也为 Codable API 设计了类型擦除类型: KeyedEncodingContainer 和 KeyedDecodingContainer。它们都是容器协议类型包装器,可用来在不知道底层具体类型信息的情况下实现 Encode 和 Decode。

三、泛型的内存结构

1. 普通的泛型参数

《闭包及其本质分析》 这篇文章中,我们把函数的类型通过源码分析出来了,那当我在这个函数加上一个泛型参数,它的内存结构是怎么样的呢?

代码如下:

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

testGenric(10)

我们将它编译成 IR 代码,IR 的语法和如何编译成 IR 代码在《闭包及其本质分析》《方法》这两篇文章中有介绍。

接下来我们就直接将当前的 main.swift 文件编译成 main.ll 文件,编译完成之后我们直接看到 testGenric(:) 函数的调用,代码如下

// %T:对于这个 testGenric(:) 函数,它已经把这个泛型 T 带进来了
define hidden swiftcc void @"main.testGenric<A>(A) -> A"(%swift.opaque* noalias nocapture sret(%swift.opaque) %0, %swift.opaque* noalias nocapture %1, %swift.type* %T) #0 {
    entry:
    // %swift.type*:HeapObject *。
    %T1 = alloca %swift.type*, align 8
    %tmp.debug = alloca i8*, align 8
    %2 = bitcast i8** %tmp.debug to i8*
    call void @llvm.memset.p0i8.i64(i8* align 8 %2, i8 0, i64 8, i1 false)
    // 把泛型参数 T 存储到 T1 里。不管是分配内存空间还是管理内存空间,都是依赖于当前的 Metadata。
    store %swift.type* %T, %swift.type** %T1, align 8
    %3 = bitcast %swift.type* %T to i8***

    // -1 取出来的是 valueWitnesses
    %4 = getelementptr inbounds i8**, i8*** %3, i64 -1

    // 这个 valueWitnesses 是一个值见证表
    // valueWitnesses 记录了 size、stride、aligment 以及内存管理的一些函数。
    %T.valueWitnesses = load i8**, i8*** %4, align 8, !invariant.load !15, !dereferenceable !16

    %5 = bitcast i8** %T.valueWitnesses to %swift.vwtable*
    %6 = getelementptr inbounds %swift.vwtable, %swift.vwtable* %5, i32 0, i32 8
    %size = load i64, i64* %6, align 8, !invariant.load !15
    %7 = alloca i8, i64 %size, align 16
    call void @llvm.lifetime.start.p0i8(i64 -1, i8* %7)
    %8 = bitcast i8* %7 to %swift.opaque*
    store i8* %7, i8** %tmp.debug, align 8
    %9 = getelementptr inbounds i8*, i8** %T.valueWitnesses, i32 2
    %10 = load i8*, i8** %9, align 8, !invariant.load !15

    %initializeWithCopy = bitcast i8* %10 to %swift.opaque* (%swift.opaque*, %swift.opaque*, %swift.type*)*
    %11 = call %swift.opaque* %initializeWithCopy(%swift.opaque* noalias %8, %swift.opaque* noalias %1, %swift.type* %T) #3
    %12 = call %swift.opaque* %initializeWithCopy(%swift.opaque* noalias %0, %swift.opaque* noalias %8, %swift.type* %T) #3
    %13 = getelementptr inbounds i8*, i8** %T.valueWitnesses, i32 1
    %14 = load i8*, i8** %13, align 8, !invariant.load !15

    %destroy = bitcast i8* %14 to void (%swift.opaque*, %swift.type*)*
    call void %destroy(%swift.opaque* noalias %8, %swift.type* %T) #3
    %15 = bitcast %swift.opaque* %8 to i8*
    call void @llvm.lifetime.end.p0i8(i64 -1, i8* %15)
    ret void
}

通过阅读这段 IR 代码我们得出,泛型函数是通过一个叫 ValueWitnessTable 的东西来管理泛型函数的内存的。我们来看一下这个 ValueWitnessTable 的内存结构,代码如下:

%swift.vwtable = type { i8*, i8*, i8*, i8*, i8*, i8*, i8*, i8*, i64, i64, i32, i32 }

一看好多个 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
}

那其实对于这个 unknow 指针是一些什么函数呢,在查资料后还原出来的大致如下:

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
}

那其实在源码中也可以搜索一下 ValueWitnessTable 的实现,可以看一下它对值类型和引用类型的操作,这里就不去阐述了。

2. 闭包作为泛型参数

接下来我们看一下把闭包当作泛型参数,它的内存结构又是怎么样的,代码如下:

func makeIncrement() -> () -> Void{
    var runningTotal = 10

    func makeInc(){
        runningTotal += 10
    }

    return makeInc
}

func test<T>(_ t: T){ }

let f = makeIncrement()
test(f)

makeIncrement 函数返回一个无参无返回值的函数,而根据 makeIncrement 的实现,最后返回的是一个闭包,接下来我们将它编译成 IR 代码来观察,我们直接看到 main 函数,代码如下:

define i32 @main(i32 %0, i8** %1) #0 {
    entry:
    %2 = alloca %swift.function, align 8
    %3 = bitcast i8** %1 to i8*
    %4 = call swiftcc { i8*, %swift.refcounted* } @"main.makeIncrement() -> () -> ()"()
    %5 = extractvalue { i8*, %swift.refcounted* } %4, 0
    %6 = extractvalue { i8*, %swift.refcounted* } %4, 1

    store i8* %5, i8** getelementptr inbounds (%swift.function, %swift.function* @"main.f : () -> ()", i32 0, i32 0), align 8

    store %swift.refcounted* %6, %swift.refcounted** getelementptr inbounds (%swift.function, %swift.function* @"main.f : () -> ()", i32 0, i32 1), align 8

    %7 = bitcast %swift.function* %2 to i8*

    call void @llvm.lifetime.start.p0i8(i64 16, i8* %7)
    %8 = load i8*, i8** getelementptr inbounds (%swift.function, %swift.function* @"main.f : () -> ()", i32 0, i32 0), align 8
    %9 = load %swift.refcounted*, %swift.refcounted** getelementptr inbounds (%swift.function, %swift.function* @"main.f : () -> ()", i32 0, i32 1), align 8
    %10 = call %swift.refcounted* @swift_retain(%swift.refcounted* returned %9) #2
    // %11:heapObject *
    %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) #2

    // 将 %11 转换成 { %swift.refcounted, %swift.function } 类型
    %12 = bitcast %swift.refcounted* %11 to <{ %swift.refcounted, %swift.function }>*
    // 将 %12 的 %swift.function 取出存到 %13
    %13 = getelementptr inbounds <{ %swift.refcounted, %swift.function }>, <{ %swift.refcounted, %swift.function }>* %12, i32 0, i32 1
    // 接下来冲 %13 取出第0个元素,也就是  %swift.function 的第一个元素。i8* - void* - 函数地址
    %.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
    // 取出 %swift.function 的第0个元素:函数地址
    %.fn1 = getelementptr inbounds %swift.function, %swift.function* %2, i32 0, i32 0

    store i8* bitcast (void (%swift.opaque*, %swift.refcounted*)* @"partial apply forwarder for reabstraction thunk helper from @escaping @callee_guaranteed () -> () to @escaping @callee_guaranteed () -> (@out ())" to i8*), i8** %.fn1, align 8
    // 取出 %swift.function 的第1个元素,存储到 {i8*, {%swift.refcounted, {i8*, i64}}}
    %.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*
    %15 = call %swift.type* @__swift_instantiateConcreteTypeFromMangledName({ i32, i32 }* @"demangling cache variable for type metadata for () -> ()") #9
    call swiftcc void @"main.gencr<A>(A) -> ()"(%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) #2
    %17 = bitcast %swift.function* %2 to i8*
    call void @llvm.lifetime.end.p0i8(i64 16, i8* %17)
    ret i32 0
}

通过观察 IR,这个闭包的被当作泛型参数之后的结构为 {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
}

接下来呢我们验证一下是否就是这个结构,把捕获到的 10 打印出来,代码如下:

func gencr<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)
}

let f = makeIncrement()
gencr(f)
打印结果:
Optional(10)

打印的结果是正确的,那也就意味着,当我们给一个泛型参数传一个函数时,这个时候 T 为函数,此时它会通过我们当前重新抽象的中间层里取到函数的地址来进行执行。

所以本质上,当我们把闭包或者函数当作泛型参数进行传值的时候,它为了使泛型的管理统一,也是重新抽象了一层中间层来捕获当前传进来的函数。

注:它是如何分辨当前传入的是闭包还是函数呢,是通过 metadata 来进行判断的。