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