存在容器与符合协议的结构体实例的关系
Relation between Existential Container and struct instance which conform protocol
我正在尝试了解如何找到协议方法的实现。
我知道 Swift 使用存在容器在堆栈内存中进行固定大小的存储,它管理如何在内存中描述 struct
的实例。它有一个价值见证 Table (VWT) 和协议见证 Table (PWT)
VWT 知道如何管理结构实例中的实际值(它们的生命周期),PWT 知道协议方法的实现。
但我想知道结构实例与“存在容器”之间的关系。
struct
的实例是否有指向存在容器的指针?
struct
的实例如何知道其存在的容器?
我的理解:
Struct 不知道存在 container/value witness table/protocol witness table 在哪里,编译器知道。如果某处需要,编译器将它们传递给那里。
前言:我不知道你有多少背景知识,所以我可能会过度解释以确保我的答案清楚。
此外,我正在尽我所能,凭记忆。我可能会混淆一些细节,但希望这个答案至少可以让您进一步阅读。
另请参阅:
在 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
调用此函数。问题是这两个对象的大小不同。这是一个问题:如果参数的大小可以不同,编译器如何知道需要为该函数的参数分配多少堆栈 space?它必须做些事情来帮助 fooUsingProtcolExistential
适应这一点。
现有容器是解决方案。当您在需要协议类型的地方传递一个值时,Swift 编译器将生成代码,自动为您将该值装箱到“存在容器”中。按照目前的定义,存在容器的大小为 4 个字:
- 第一个词是指向协议见证的指针Table(稍后会详细介绍)
- 接下来的三个字是值的内联存储。
当存储的值小于 3 个字时(例如 SomeConformerSmall
),该值直接内联打包到 3 个字的缓冲区中。如果该值的大小超过 3 个字(例如 SomeConformerSmall
),则会在堆上分配一个 ARC 管理的框,并将该值复制到那里。然后将指向此框的指针复制到存在容器的第一个字中(最后两个字未使用,IIRC)。
这引入了一个新问题:假设 fooUsingProtcolExistential
想要将其参数转发给另一个函数。它应该如何通过 EC? fooUsingProtcolExistential
不知道 EC 是否包含内联值(在这种情况下,通过 EC 只需要复制其 4 个内存字),或堆分配(在这种情况下,通过 EC 还需要一个ARC 保留在该堆分配的缓冲区上)。
为了解决这个问题,协议见证 Table 包含一个指向价值见证 Table (VWT) 的指针。每个 VWT 都定义了一组标准的函数指针,定义了如何分配、复制、删除 EC 等。每当需要以某种方式操纵存在的协议时,VWT 都会准确定义如何操作。
所以现在我们有了一个固定大小的容器(它解决了我们的异类大小参数传递问题),以及一种移动容器的方法。我们实际上可以用它做什么?
至少,此协议类型的值必须至少定义协议定义的必需成员(初始化程序、属性(存储或计算)、函数和下标)。
但是每个符合的类型可能以不同的方式实现这些成员。例如。某些结构可能通过直接定义方法来满足方法要求,但另一个 class 可能通过从 superclass 继承方法来满足它。有些人可能将 属性 实现为存储的 属性,其他人将其实现为计算的 属性,等等
处理这些不兼容性是 Protocol Witness Table 的主要目的。每个协议一致性都有一个这些表(例如,一个用于 SomeConformerSmall
,一个用于 SomeConformerBig
)。它们包含一组函数指针,指向协议要求的实现。虽然指向的功能可能在不同的地方,但 PWT 的布局是一致的,因为协议是符合的。因此,fooUsingProtcolExistential
能够查看 EC 的 PWT,并使用它来查找协议方法的实现,并调用它。
简而言之:
- 一个 EC 包含一个 PWT 和一个值(内联或间接)
- PWT 指向 VWT
我正在尝试了解如何找到协议方法的实现。
我知道 Swift 使用存在容器在堆栈内存中进行固定大小的存储,它管理如何在内存中描述 struct
的实例。它有一个价值见证 Table (VWT) 和协议见证 Table (PWT)
VWT 知道如何管理结构实例中的实际值(它们的生命周期),PWT 知道协议方法的实现。
但我想知道结构实例与“存在容器”之间的关系。
struct
的实例是否有指向存在容器的指针?
struct
的实例如何知道其存在的容器?
我的理解:
Struct 不知道存在 container/value witness table/protocol witness table 在哪里,编译器知道。如果某处需要,编译器将它们传递给那里。
前言:我不知道你有多少背景知识,所以我可能会过度解释以确保我的答案清楚。
此外,我正在尽我所能,凭记忆。我可能会混淆一些细节,但希望这个答案至少可以让您进一步阅读。
另请参阅:
在 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
调用此函数。问题是这两个对象的大小不同。这是一个问题:如果参数的大小可以不同,编译器如何知道需要为该函数的参数分配多少堆栈 space?它必须做些事情来帮助 fooUsingProtcolExistential
适应这一点。
现有容器是解决方案。当您在需要协议类型的地方传递一个值时,Swift 编译器将生成代码,自动为您将该值装箱到“存在容器”中。按照目前的定义,存在容器的大小为 4 个字:
- 第一个词是指向协议见证的指针Table(稍后会详细介绍)
- 接下来的三个字是值的内联存储。
当存储的值小于 3 个字时(例如 SomeConformerSmall
),该值直接内联打包到 3 个字的缓冲区中。如果该值的大小超过 3 个字(例如 SomeConformerSmall
),则会在堆上分配一个 ARC 管理的框,并将该值复制到那里。然后将指向此框的指针复制到存在容器的第一个字中(最后两个字未使用,IIRC)。
这引入了一个新问题:假设 fooUsingProtcolExistential
想要将其参数转发给另一个函数。它应该如何通过 EC? fooUsingProtcolExistential
不知道 EC 是否包含内联值(在这种情况下,通过 EC 只需要复制其 4 个内存字),或堆分配(在这种情况下,通过 EC 还需要一个ARC 保留在该堆分配的缓冲区上)。
为了解决这个问题,协议见证 Table 包含一个指向价值见证 Table (VWT) 的指针。每个 VWT 都定义了一组标准的函数指针,定义了如何分配、复制、删除 EC 等。每当需要以某种方式操纵存在的协议时,VWT 都会准确定义如何操作。
所以现在我们有了一个固定大小的容器(它解决了我们的异类大小参数传递问题),以及一种移动容器的方法。我们实际上可以用它做什么?
至少,此协议类型的值必须至少定义协议定义的必需成员(初始化程序、属性(存储或计算)、函数和下标)。
但是每个符合的类型可能以不同的方式实现这些成员。例如。某些结构可能通过直接定义方法来满足方法要求,但另一个 class 可能通过从 superclass 继承方法来满足它。有些人可能将 属性 实现为存储的 属性,其他人将其实现为计算的 属性,等等
处理这些不兼容性是 Protocol Witness Table 的主要目的。每个协议一致性都有一个这些表(例如,一个用于 SomeConformerSmall
,一个用于 SomeConformerBig
)。它们包含一组函数指针,指向协议要求的实现。虽然指向的功能可能在不同的地方,但 PWT 的布局是一致的,因为协议是符合的。因此,fooUsingProtcolExistential
能够查看 EC 的 PWT,并使用它来查找协议方法的实现,并调用它。
简而言之:
- 一个 EC 包含一个 PWT 和一个值(内联或间接)
- PWT 指向 VWT