1

我试图了解如何找到协议方法的实现。

我知道 Swift 使用 Existential Container 在堆栈内存中进行固定大小的存储,它管理如何描述内存中的实例struct。它有一个价值见证表(VWT)和协议见证表(PWT)

VWT 知道如何在结构实例(它们的生命周期)中管理实际价值,而 PWT 知道协议方法的实现。

但我想知道结构实例和“存在容器”之间的关系。

的实例是否struct有一个指向存在容器的指针?

a 的实例如何struct知道它的存在容器?

4

2 回答 2

1

前言:我不知道你有多少背景知识,所以我可能会过度解释以确保我的答案清楚。

另外,我正在尽我的能力做这件事,记忆犹新。我可能会混淆一些细节,但希望这个答案至少可以引导您进一步阅读。

也可以看看:


在 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,并使用它来找到协议方法的实现,并调用它。

简而言之:

  • EC 包含一个 PWT 和一个值(内联或间接)
  • PWT 指向 VWT
于 2021-06-17T17:29:25.773 回答
0

我的理解:

结构不知道存在容器/值见证表/协议见证表在哪里,编译器知道。如果需要,编译器会将它们传递到那里。

于 2021-06-17T16:57:43.013 回答