Any metadata 的内存布局

Swiftenumstructclassprotocol都有对应的metadata

metadata用来描述上述这些类型本身,这点和OC的元类有点类似。

1 直观感受 metadata

假设用下面代码定义了一个enum:

swift 复制代码
enum Direction {
  case top
  case left
  case bottom
  case right
}

经过编译链接之后,上面的enum Direction以及对应的metadata都写入到了对应的Mach-O文件中。

假设编译链接之后的Mach-O文件名为A,那么,使用下面命令就可以查看enum Direction以及对应metadata的位置:

bash 复制代码
nm -n A | xcrun swift-demangle | grep -i Direction

nm 命令读取Mach-O中的所有符号信息。

swift-demangle对已经mangleswift符号名进行demangle

输出的结果如下:

bash 复制代码
000000000001e004 s _symbolic _____ 7iOSTest9DirectionO
0000000000020820 s full type metadata for iOSTest.Direction
0000000000020830 S type metadata for iOSTest.Direction

1列的数字代表对应项在Mach-O文件中的偏移。

输出的第1行的符号名7iOSTest9DirectionO有点怪。

这是因为swift-demangle没有彻底demangle干净,我们再次手动demangle下:

bash 复制代码
xcrun swift-demangle s7iOSTest9DirectionO

注意我们在符号名字前加了一个小写字母s

Swift会在mangle名前加一个字母s,代表Swift

再次demangle的输出结果为:

bash 复制代码
$s7iOSTest9DirectionO ---> iOSTest.Direction

可以看到,它正是enum Direction本身,位于0x1e004

输出第3行可以看到enum Directionmetadata位于0x20830

输出的第2行还有一个full type metadata,这是什么呢?

Swift里,一个类型的metadata前面根据类型的不同,会有一些其他数据。

其中任何类型都有一个数据是value witness table指针。

value witness tableSwift用来管理类型的生命周期,比如怎么复制、怎么销毁。

可以看到,所谓的full type metadata就是「其他数据」+对应的type metadata

而我们平时使用的.self语法,得到的就是metadata本身:

bash 复制代码
(lldb) p unsafeBitCast(Direction.self, to: UnsafeRawPointer.self)
(UnsafeRawPointer) 0x100adc830
(lldb) p/x 0x100adc830 - 0x0000000100abc000
(Int) 0x0000000000020830

从上面lldb的输出看到,经过ASLR的调整,Direction.self的位置正是0x20830

2 Any 的 metadata

Any是一个protocol,如下面输出:

bash 复制代码
(lldb) p Any.self
(Any.Protocol) Any

Any还是一个空协议组合,表示「没有任何要求」。

而「没有任何要求」是任何类型都满足的,这也是任何类型都能转成Any的原因。

2.1 Any metadata 的定义

Anymetadata定义为:

c 复制代码
/// stdlib/public/runtime/metadata.cpp
/// The standard metadata for Any.
const FullMetadata<ExistentialTypeMetadata> swift::
METADATA_SYM(ANY_MANGLING) = {
  { &OpaqueExistentialValueWitnesses_0 }, // ValueWitnesses
  ExistentialTypeMetadata(
    ExistentialTypeFlags() // Flags
      .withNumWitnessTables(0)
      .withClassConstraint(ProtocolClassConstraint::Any)
      .withHasSuperclass(false)
      .withSpecialProtocol(SpecialProtocol::None)),
};

上面的定义准确的说,是Anyfull metadata

定义中的FullMetadata是一个C++结构体,定义为:

c 复制代码
/// swift/ABI/metadata.h

template <class T> struct FullMetadata : T::HeaderType, T {
  ...
};

其中模版参数T = ExistentialTypeMetadata

我们手动代入后,FullMetadata的完整定义为:

c 复制代码
struct FullMetadata : ExistentialTypeMetadata::HeaderType, ExistentialMetadata {
  ...
};

从上面代码可以看到,FullMetadata是多重继承。

一部分继承自ExistentialTypeMetadata::HeaderType

一部分继承ExistentialTypeMetadata

要想弄清楚ExistentialTypeMetadata::HeaderType,首先得看下ExistentialTypeMetadata的定义:

c 复制代码
/// stdlib/public/runtime/metadata.cpp

using ExistentialTypeMetadata
  = TargetExistentialTypeMetadata<InProcess>;

从代码上可以看到,ExistentialTypeMetadata实际上是TargetExistentialTypeMetadata<InProcess>

接着看TargetExistentialTypeMetadata的定义:

c 复制代码
/// swift/ABI/metadata.h

template <typename Runtime>
struct TargetExistentialTypeMetadata
  : TargetMetadata<Runtime>,
    swift::ABI::TrailingObjects<
      TargetExistentialTypeMetadata<Runtime>,
      ConstTargetMetadataPointer<Runtime, TargetMetadata>,
      TargetProtocolDescriptorRef<Runtime>> {
  using HeaderType = TargetTypeMetadataHeaderBase<Runtime>;
  ...

public:
  using StoredPointer = typename Runtime::StoredPointer;
  /// The number of witness tables and class-constrained-ness of the type.
  ExistentialTypeFlags Flags;

  /// The number of protocols.
  uint32_t NumProtocols;

  ...
};

从代码里可以看到,TargetExistentialTypeMetadata同样是多重继承。

一部分继承自TargetMetadata

一部分继承自swift::ABI::TrailingObjects

swift::ABI::TrailingObjects结构体没有任何实例变量,可以先不用关心。

swift::ABI::TrailingObjects的定义如下:

c 复制代码
/// swift/ABI/TrailingObjects.h

emplate <typename BaseTy, typename... TrailingTys>
class swift_ptrauth_struct_derived(BaseTy) TrailingObjects
    : private trailing_objects_internal::TrailingObjectsImpl<
                            trailing_objects_internal::AlignmentCalcHelper<
                                TrailingTys...>::Alignment,
                            BaseTy, TrailingObjects<BaseTy, TrailingTys...>,
                            BaseTy, TrailingTys...> {
  ...
};

我们继续回到TargetExistentialTypeMetadata结构体上来。

TargetExistentialTypeMetadata内部定义了HeaderType:

c 复制代码
using HeaderType = TargetTypeMetadataHeaderBase<Runtime>;

从定义上看,TargetExistentialTypeMetadata::HeaderType实际上是TargetTypeMetadataHeaderBase

呜~~~

现在,FullMetadata完整定义可以重写为:

c 复制代码
struct FullMetadata : TargetTypeMetadataHeaderBase, TargetExistentialTypeMetadata {
  ...
};

2.2 内存布局

有了继承关系,就可以画出full metadata的内存布局。

2.2.1 ValueWitness

ValueWitness就是value witness table指针,8字节。

2.2.2 Kind

Kind 是一个标识符,代表了当前是什么类型,8字节。

Kind的定义为:

c 复制代码
/// swift/ABI/MetadataValues.h

enum class MetadataKind : uint32_t {
#define METADATAKIND(name, value) name = value,
#define ABSTRACTMETADATAKIND(name, start, end)                                 \
  name##_Start = start, name##_End = end,
#include "MetadataKind.def"
  LastEnumerated = 0x7FF,
};

注意看,这里的MetadataKind32bit的,而Kind本身是8字节的。

之所以这么设计,是因为如果一个class可以与OC交互,此时Kind要当成isa指针看待。

完整的Kind定义在文件MetadaKind.def中。

常见的Kind如下:

复制代码
// 纯 Swift 类
class - 0x0
struct - 0x200
enum - 0x201
optional - 0x202
opaqual - 0x300
tuple - 0x301
existential - 0x303

2.2.3 Flags

Flags是一个C++类,定义如下:

c 复制代码
/// swift/ABI/MetadataValues.h

class ExistentialTypeFlags {
public:
  typedef uint32_t int_type;

private:
  enum : int_type {
    NumWitnessTablesMask  = 0x00FFFFFFU,
    ClassConstraintMask   = 0x80000000U, // Warning: Set if NOT class-constrained!
    HasSuperclassMask     = 0x40000000U,
    SpecialProtocolMask   = 0x3F000000U,
    SpecialProtocolShift  = 24U,
  };
  int_type Data;

它里面只有一个实例变量Data,占用4字节32bit

2.3.4 numOfProtocols

numOfProtocols占用4字节。

相关推荐
四眼蒙面侠19 小时前
深入 SwiftWork(第 0 篇):用 SwiftUI 构建一个 Agent 可视化工作台
swift·openagentsdk
sakiko_1 天前
UIKit学习笔记4-使用UITableView制作滚动视图
笔记·学习·ios·swift·uikit
四眼蒙面侠2 天前
深入 Open Agent SDK(番外篇):实战验证——把 SDK 塞进一个 macOS 原生 Agent 应用
swift·claudecode·bmad·agentsdk·openagentsdk
2501_915106323 天前
在Mac上搭建iOS开发环境的详细步骤与注意事项
ide·vscode·macos·ios·个人开发·swift·敏捷流程
harder3213 天前
RMP模式的创新突破
开发语言·学习·ios·swift·策略模式
sakiko_4 天前
UIKit学习笔记2-组件嵌套、滚动视图等
笔记·学习·objective-c·swift·uikit
四眼蒙面侠4 天前
深入 Open Agent SDK(五):会话持久化与安全防线
swift·claudecode·bmad·openagentsdk
茶底世界之下5 天前
诡异!String 参数在闭包里变成了 <uninitialized>,我排查了整整两天
ios·xcode·swift
四眼蒙面侠5 天前
深入 Open Agent SDK(四):多 Agent 协作——子代理、团队与任务编排
swift·agentsdk·openagentsdk
东坡肘子5 天前
Swift 并发正被更广泛地接纳 -- 肘子的 Swift 周报 #133
人工智能·swiftui·swift