在Swift中,如何获得一个' Any '变量的真实大小?



我希望能够获得Swift中Any变量的底层数据类型的大小。我期望通过运行MemoryLayout.size(ofValue: anyObject)来实现这一点,但是无论Any对象的底层数据类型如何,该表达式总是返回32。我假设32是内部Any构造/类型的大小,它保存了关于它存储的对象的元数据。

如何获得底层数据类型的大小?

let regularInt: Int = 1
let anyInt: Any = Int(2) as Any
MemoryLayout<Int>.size                 // 4
MemoryLayout<type(of: anyInt)>.size    // Can't do that
MemoryLayout.size(ofValue: regularInt) // 4
MemoryLayout.size(ofValue: anyInt)     // 32
// How do I get size "4" out of `anyInt`?

在本例中,我将从Any限制的一些技术细节开始。

那么Any是什么?它是一个空协议,每个类型都隐式地遵守它。

编译器如何表示协议类型的变量?它是通过将实际值包装在存在容器中。所以基本上当你引用这种类型的变量时,你实际上是在和容器对话(好吧,实际上不是你,而是编译器:)。

存在容器的布局可以用下面的C结构体来表示:

struct OpaqueExistentialContainer {
void *fixedSizeBuffer[3];
Metadata *type;
WitnessTable *witnessTables[NUM_WITNESS_TABLES];
};

在本文档中对容器元素进行了详细的解释,我也将尝试在这里总结它们:

  • fixedSizeBuffer保存整个值(如果小于24字节),或者保存一个指向堆分配区域的指针(包含值
  • )。
  • type是指向元数据类型的指针
  • witnessTables是使这个布局占用各种大小的原因,因为协议见证表的数量可以从零到几乎任何数量的协议。

因此,考虑到以上内容:

  • Any不需要见证表,因此占用32字节
  • 单个协议变量占用40个字节
  • 一个组合协议变量占用32 + N*8,其中N为"独立的"变量个数。
  • 组成中涉及的协议

请注意,如果不涉及类协议,则上述是正确的,如果涉及类协议,则存在容器布局会稍微简化,这在上面的链接文档中也有更好的描述。


现在,回到问题的问题,它是由编译器创建的存在容器它阻止你访问实际类型。编译器不会使这个结构可用,并通过存储在容器中的见证表透明地将任何对协议需求的调用转换为分派。

但是,我可以问你,为什么你流通Any?我假设您不希望以通用的方式处理所有可能的和未来的类型。标记协议可能会有所帮助:

protocol MemoryLayouted { }
extension MemoryLayouted {
var memoryLayoutSize: Int { MemoryLayout.size(ofValue: self) }
}

那么你剩下要做的就是为你想要支持的类型添加一致性:

extension Int: MemoryLayouted { }
extension String: MemoryLayouted { }
extension MyAwesomeType: MemoryLayouted { }
考虑到上述内容,您可以将初始代码重写为如下内容:
let regularInt: Int = 1
let anyInt: MemoryLayouted = 2
print(regularInt.memoryLayoutSize) // 8
print(anyInt.memoryLayoutSize)     // 8

您将获得一致的行为和类型安全,类型安全可能会转化为更稳定的应用程序。


注:一种允许使用Any的hack方法可能是通过直接内存访问解压缩存在容器。Swift ABI目前是稳定的,所以现有的容器布局保证在未来不会改变,但是除非绝对必要,否则不建议采用这种方式。

也许碰到这个问题并且有ABI布局代码经验的人可以提供相应的代码。

我要做的是将Any转换为所有支持的类型。当你知道Int是什么类型时,为什么要将它转换为Any!呢?

var value: Any = Int(2) as Any
switch value {
case value is Int:
// ... other cases here
} 

最新更新