在 Swift 中,如何获取“Any”变量的真实大小?

mar*_*tin 6 size types swift

我希望能够获取AnySwift 中变量的基础数据类型的大小。我预计这可以通过运行来实现MemoryLayout.size(ofValue: anyObject),但该表达式始终返回32,无论对象的基础数据类型如何Any。我假设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`?
Run Code Online (Sandbox Code Playgroud)

Cri*_*tik 5

我将从一些有关本例中的局限性的技术细节开始Any

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

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

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

struct OpaqueExistentialContainer {
  void *fixedSizeBuffer[3];
  Metadata *type;
  WitnessTable *witnessTables[NUM_WITNESS_TABLES];
};
Run Code Online (Sandbox Code Playgroud)

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

  • fixedSizeBuffer要么保存整个值(如果它少于 24 个字节),要么保存指向包含该值的堆分配区域的指针
  • type是指向类型元数据的指针
  • witnessTables是什么使得这种布局占据不同的大小,因为协议见证表的数量可以从零到几乎任意数量的协议变化。

因此,考虑到上述内容:

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

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


现在,从问题回到问题,编译器创建的存在容器阻止您访问实际类型。编译器不会使此结构可用,并透明地将任何对协议要求的调用转换为通过存储在容器中的见证表进行调度。

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

protocol MemoryLayouted { }

extension MemoryLayouted {
    var memoryLayoutSize: Int { MemoryLayout.size(ofValue: self) }
}
Run Code Online (Sandbox Code Playgroud)

然后您剩下要做的就是为您想要支持的类型添加一致性:

extension Int: MemoryLayouted { }
extension String: MemoryLayouted { }
extension MyAwesomeType: MemoryLayouted { }
Run Code Online (Sandbox Code Playgroud)

考虑到上述内容,您可以将初始代码重写为如下所示:

let regularInt: Int = 1
let anyInt: MemoryLayouted = 2

print(regularInt.memoryLayoutSize) // 8
print(anyInt.memoryLayoutSize)     // 8
Run Code Online (Sandbox Code Playgroud)

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


PS 一种允许您使用的 hacky 方法Any可能可以通过直接内存访问解压存在容器来实现。Swift ABI 目前是稳定的,因此现有的容器布局保证将来不会改变,但除非绝对必要,否则不建议走这条路。

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