简体   繁体   English

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

[英]In Swift, how to get the true size of an `Any` variable?

I want to be able to get the size of the underlying data type of an Any variable in Swift. I expected this to be possible by running MemoryLayout.size(ofValue: anyObject) , but that expression always returns 32 , regardless of the the underlying data type of the Any object. I assume 32 is the size of the internal Any construct/type, which holds metadata about the object it stores.我希望能够获取 Swift 中Any变量的基础数据类型的大小。我希望通过运行MemoryLayout.size(ofValue: anyObject)可以实现这一点,但是该表达式始终返回32 ,而不管基础Any object 的数据类型。我假设32是内部Any构造/类型的大小,它包含有关它存储的 object 的元数据。

How do I get the underlying data type's size?如何获取基础数据类型的大小?

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`?

I'll begin with some technical details about the limitations of Any in this case.我将从一些关于Any在这种情况下的局限性的技术细节开始。

So, what is Any ?那么,什么是Any It's an empty protocol to which every type implicitly conforms to.它是一个空协议,每个类型都隐含地遵守它。

And how does the compiler represent variables of protocol types?编译器如何表示协议类型的变量? It's by wrapping the actual value in an existential container.它是通过将实际值包装在一个存在的容器中。 So basically when you're referencing a variable of this kind, you're actually talking to the container (well, actually not you, but the compiler is:).所以基本上,当您引用此类变量时,您实际上是在与容器对话(好吧,实际上不是您,而是编译器:)。

An existential container has a layout that can be represented like this C structure:存在容器的布局可以表示为 C 结构:

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

The container elements are greatly explained in this document , I'll also try to summarize them here:容器元素在本文档中得到了很好的解释,我也将在这里尝试对其进行总结:

  • fixedSizeBuffer either holds the whole value, if it takes less than 24 bytes, or holds a pointer to a heap allocated zone, containing the value fixedSizeBuffer要么保存整个值,如果它少于 24 个字节,要么保存指向堆分配区域的指针,其中包含该值
  • type is a pointer to the type metadata type是指向类型元数据的指针
  • witnessTables is what makes this layout occupy various sizes, as the number of protocol witness tables can vary from zero to virtually any number of protocols. witnessTables是使此布局占据各种大小的原因,因为协议见证表的数量可以从零到几乎任意数量的协议不等。

So, with the above in mind:所以,考虑到以上几点:

  • Any needs no witness tables, thus it occupies 32 bytes Any不需要 witness 表,因此它占用 32 个字节
  • a single protocol variable occupies 40 byes单个协议变量占用40字节
  • a composed protocol variable occupies 32 + N*8, where N is the number of "independent" protocols involved in the composition一个组合的协议变量占用32 + N*8,其中N是组合中涉及的“独立”协议的数量

Note that the above is true if there are no class protocols involved, if a class protocol is involved, then the existential container layout is a little bit simplified, this is also better described in the linked document from above.请注意,如果不涉及 class 协议,则上述内容成立,如果涉及 class 协议,则现有容器布局会稍微简化一些,这在上面的链接文档中也有更好的描述。


Now, back to the problem from the question, it's the existential container created by the compiler the one which prevents you from accessing the actual type.现在,回到问题的问题,它是编译器创建的存在容器,它阻止您访问实际类型。 The compiler doesn't make this structure available, and transparently translates any calls to protocol requirements to dispatches through the witness tables stored in the container.编译器不提供此结构,并且透明地将任何对协议要求的调用转换为通过存储在容器中的见证表进行分派。

But, might I ask you, why are you circulating Any ?但是,我可以问你,你为什么要传播Any I assume you don't want to handle all possible and future types in a generic manner.我假设您不想以通用方式处理所有可能的和未来的类型。 A marker protocol might help here:标记协议可能在这里有所帮助:

protocol MemoryLayouted { }

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

Then all you have left to do is to add conformance for the types you want to support:然后你剩下要做的就是为你想要支持的类型添加一致性:

extension Int: MemoryLayouted { }
extension String: MemoryLayouted { }
extension MyAwesomeType: MemoryLayouted { }

With the above in mind, you can rewrite your initial code to something like this:考虑到上述情况,您可以将初始代码重写为如下内容:

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

print(regularInt.memoryLayoutSize) // 8
print(anyInt.memoryLayoutSize)     // 8

You get consistent behaviour and type safety, a type safety that might translate to a more stable application.您将获得一致的行为和类型安全,这种类型安全可能会转化为更稳定的应用程序。


PS A hacky approach, that allows you to use Any , might pe possible by unpacking the existential container via direct memory access. PS 一种允许您使用Any的 hacky 方法可能通过直接 memory 访问解压现有容器来实现。 The Swift ABI is stable at this point, so the existential container layout is guaranteed not to change in the future, however not recommending going that route unless absolutely necessary. Swift ABI 在这一点上是稳定的,所以现有的容器布局保证在未来不会改变,但是除非绝对必要,否则不建议走这条路。

Maybe someone that stumbles this question and has experience in the ABI layout code can provide the code for it.也许遇到这个问题并且有 ABI 布局代码经验的人可以提供它的代码。

What I would do is cast Any to all supported types.我要做的是将 Any 转换为所有支持的类型。 Why would you cast Int as Any when you know what type is it?当您知道 Int 是什么类型时,为什么还要将其转换为Any anyway?反正?

var value: Any = Int(2) as Any

switch value {

case value is Int:
// ... other cases here
} 

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM