Don*_*Lee 3 existential-type method-dispatch swift
我试图了解如何找到协议方法的实现。
我知道 Swift 使用存在容器在堆栈内存中进行固定大小的存储,它管理如何描述内存中的实例struct。它有一个值见证表(VWT)和协议见证表(PWT)
VWT 知道如何管理结构实例中的实际值(它们的生命周期),PWT 知道协议方法的实现。
但我想知道结构体实例和“存在容器”之间的关系。
的实例是否struct有一个指向存在容器的指针?
a 的实例如何struct知道它的存在容器?
Ale*_*ica 11
前言:我不知道你有多少背景知识,所以我可能会过度解释以确保我的答案清楚。
另外,我正在尽我所能,凭记忆做这件事。我可能会混淆一些细节,但希望这个答案至少可以引导您进一步阅读。
也可以看看:
在 Swift 中,协议可以“作为类型”使用,或者作为通用约束。后一种情况看起来像这样:
protocol SomeProtocol {}
struct SomeConformerSmall: SomeProtocol {
// No ivars
}
struct SomeConformerBig: SomeProtocol {
let a, b, c, d, e, f, g: Int // Lots of ivars
}
func fooUsingGenerics<T: SomeProtocol>(_: T) {}
let smallObject = SomeConformerSmall()
let bigObject = SomeConformerBig()
fooUsingGenerics(smallObject)
fooUsingGenerics(bigObject)
Run Code Online (Sandbox Code Playgroud)
该协议用作编译时类型检查的约束,但在运行时(大多数情况下)没有什么特别的情况发生。大多数时候,编译器会生成该foo函数的单态变体,就像您已经定义了fooUsingGenerics(_: SomeConformerSmall)orfooUsingGenerics(_: SomeConformerBig)一样。
当协议“像类型一样使用”时,它看起来像这样:
func fooUsingProtcolExistential(_: SomeProtocol) {}
fooUsingGenerics(smallObject)
fooUsingGenerics(bigObject)
Run Code Online (Sandbox Code Playgroud)
如您所见,可以使用smallObject和来调用该函数bigObject。问题是这两个对象的大小不同。这是一个问题:如果参数可以不同大小,编译器如何知道需要为该函数的参数分配多少堆栈空间?它必须采取一些措施来帮助fooUsingProtcolExistential适应这种情况。
存在的容器就是解决方案。当您传递一个需要协议类型的值时,Swift 编译器将生成代码,自动将该值装箱到“存在容器”中。按照目前的定义,存在容器的大小为 4 个字:
当存储的值的大小小于 3 个字时(例如SomeConformerSmall),该值将直接内联打包到该 3 个字缓冲区中。如果该值的大小超过 3 个字(例如SomeConformerSmall),则在堆上分配一个 ARC 管理的框,并将该值复制到那里。然后,指向该框的指针被复制到存在容器的第一个字中(最后 2 个字未使用,IIRC)。
这引入了一个新问题:假设想要fooUsingProtcolExistential将其参数转发到另一个函数。应该如何通过EC?fooUsingProtcolExistential不知道 EC 是否包含内联值(在这种情况下,传递 EC 仅需要复制其 4 个内存字),或者堆分配值(在这种情况下,传递 EC 还需要在该堆上保留 ARC -分配的缓冲区)。
为了解决这个问题,协议见证表包含一个指向值见证表(VWT)的指针。每个 VWT 都定义了一组标准的函数指针,这些指针定义了如何分配、复制、删除 EC。每当需要以某种方式操作协议存在时,VWT 都会准确定义如何操作。
现在我们有了一个恒定大小的容器(它解决了异构大小的参数传递问题),以及移动容器的方法。我们实际上可以用它做什么?
至少,此协议类型的值必须至少定义协议定义的所需成员(初始化程序、属性(存储或计算)、函数和下标)。
但每个符合类型可能以不同的方式实现这些成员。例如,某些结构可能通过直接定义方法来满足方法要求,但另一个类可能通过从超类继承该方法来满足它。有些可能将属性实现为存储属性,其他可能实现为计算属性,等等。
处理这些不兼容性是协议见证表的主要目的。每个协议一致性都有这些表之一(例如,一张用于SomeConformerSmall,一张用于SomeConformerBig)。它们包含一组函数指针,指向协议要求的实现。虽然所指向的功能可能位于不同的位置,但 PWT 的布局对于所遵循的协议而言是一致的。因此,fooUsingProtcolExistential能够查看 EC 的 PWT,并使用它来查找协议方法的实现,并调用它。
简而言之: