Swift 是如何管理协议类型与泛型类型的生命周期与方法调用?
协议类型在内存中的存储形式是 Extential Container,Extential Container 占 5 个内存单元(也称 词),其作用如下:
- 3 个词作为 Value Buffer。
- 1 个词作为 Value Witness Table 的索引,主要用于管理生命周期。
- 1 个词作为 Protocol Witness Table 的索引,主要用于管理方法调用。
泛型类型由于在调用时能够确定具体的类型,所以不需要使用 Extential Container。在调用泛型方法时,只需要将 Value Witness Table/Protocol Witness Table 作为额外参数进行传递。
内存管理
泛型类型使用 VWT 进行内存管理,VWT 由编译器生成,其存储了该类型的 size、aligment(对齐方式)以及针对该类型的基本内存操作。其结构如下所示(以 C 代码表示):
struct value_witness_table {
size_t size, align;
void (*copy_init)(opaque *dst, const opaque *src, type *T);
void (*copy_assign)(opaque *dst, const opaque *src, type *T);
void (*move_init)(opaque *dst, const opaque *src, type *T);
void (*move_assign)(opaque *dst, const opaque *src, type *T);
void (*destroy)(opaque *val, type *T);
}
当对泛型类型进行内存操作(如:内存拷贝)时,最终会调用对应泛型类型的 VWT 中的基本内存操作。泛型类型不同,其对应的 VWT 也不同。下图所示为一个小的值类型和一个引用类型的 VWT。
- 对于一个小的值类型,如:integer。该类型的 copy 和 move 操作会进行内存拷贝;destroy 操作则不进行任何操作。
- 对于一个引用类型,如:class。该类型的 copy 操作会对引用计数加 1;move 操作会拷贝指针,而不会更新引用计数;destroy 操作会对引用计数减 1。
方法调用
上一节,我们介绍了泛型的内存管理。那么,泛型的方法调用又是如何实现的呢?
我们以如下一个泛型函数为例进行介绍。
func f<T>(_ t: T) -> T {
let copy = t
return copy
}
编译器对上述的泛型函数进行编译后,会得到如下代码(以 C 代码代替 LLVM IR)。
// T: 泛型参数
void f(opaque *result, opaque *t, type *T) {
opaque *copy = alloca(T->vwt->size);
T->vwt->copy_init(copy, t, T