初探Swift底层Metadata

3,389 阅读6分钟

前言

本文将会初次探索Swift底层,但随着Swift版本更新,底层结构可能会变动(ABI已经稳定,即使调整,应该也是微调),所以在这边记录下版本号。

Swift源码版本是5.3.1Xcode版本是12.3(其实对应的源码版本是5.3.2,不过小版本,懒的更新了)

本文会初步探索Metadata,详细的底层结构会在文章末尾附上链接。

由于比较深入底层,会有较多指针类型,如果你不是很熟悉Swift类型的指针,可以先看下我上一篇写的文章

通过Mirror获取类型的Metadata

虽然Swift重心在强调静态类型上,但它通过反射MirrorAPI,允许代码在运行时检查和操作任意值。这边我们通过Mirror的源码,看如何获取数据类型的元数据Metadata的,我们先以struct为突入口。

我们在源码中运行如下代码

struct Teacher {var age = 18; var name = "kody"}

let t = Teacher.init()

let mirror = Mirror(reflecting: t)

在初始化mirror前,我们在init(reflecting subject: Any)方法中打上断点,随着断点走:

我们可以看到这些框起来的方法(获取类型,及属性个数)都调用了一个call,然后拿到了一个impl变量,然后返回impl的某个属性就可以了,所以这边call方法和impl是什么比较关键,我们继续往下走:

先进入call方法(部分截图,太长了):

初步看到implReflectionMirrorImpl,这里是一个类似闭包的东西,参数传了一个ReflectionMirrorImpl类型,接着往下走,看看谁会调用:

我们发现通过调用type->getKind()获取数据类型,发现是一个结构体,然后进入switch的结构体的分支,发现impl变成了StructImpl,我们搜索StructImpl可以看到

从图中我们可以发现:StructImplReflectionMirrorImpl的子类,Struct的元数据MetadataStructMetadata

简单的整理下Mirror底层的核心逻辑,就是通过getKind()方法获取该类型的元数据类型,然后根据该类型Metadata获取相应的属性,比如类型名称、属性名字,属性个数等

MetadataKind

前面在type->getKind()switch中,我们看到了MetadataKind::Struct这个类型。

我们看下MetadataKind类型:

enum class MetadataKind : uint32_t

MetadataKind是一个Int32大小的枚举,我们看下MetadataKind::Struct系列的对应的枚举值

const unsigned MetadataKindIsNonType = 0x400;
const unsigned MetadataKindIsNonHeap = 0x200;
const unsigned MetadataKindIsRuntimePrivate = 0x100;

LastEnumerated = 0x7FF,

NOMINALTYPEMETADATAKIND(Class, 0)
NOMINALTYPEMETADATAKIND(Struct, 0 | MetadataKindIsNonHeap)
NOMINALTYPEMETADATAKIND(Enum, 1 | MetadataKindIsNonHeap)
NOMINALTYPEMETADATAKIND(Optional, 2 | MetadataKindIsNonHeap)
METADATAKIND(ForeignClass, 3 | MetadataKindIsNonHeap)
METADATAKIND(Opaque, 0 | MetadataKindIsRuntimePrivate | MetadataKindIsNonHeap)
METADATAKIND(Tuple, 1 | MetadataKindIsRuntimePrivate | MetadataKindIsNonHeap)
METADATAKIND(Function, 2 | MetadataKindIsRuntimePrivate | MetadataKindIsNonHeap)
METADATAKIND(Existential, 3 | MetadataKindIsRuntimePrivate | MetadataKindIsNonHeap)
METADATAKIND(Metatype, 4 | MetadataKindIsRuntimePrivate | MetadataKindIsNonHeap)
METADATAKIND(ObjCClassWrapper, 5 | MetadataKindIsRuntimePrivate | MetadataKindIsNonHeap)
METADATAKIND(ExistentialMetatype, 6 | MetadataKindIsRuntimePrivate | MetadataKindIsNonHeap)
METADATAKIND(HeapLocalVariable, 0 | MetadataKindIsNonType)
METADATAKIND(HeapGenericLocalVariable, 0 | MetadataKindIsNonType | MetadataKindIsRuntimePrivate)
METADATAKIND(ErrorObject, 1 | MetadataKindIsNonType | MetadataKindIsRuntimePrivate)
             

我们可以把Kind整理成一张表格

名称枚举值说明
Class0x0
Struct0x200结构体
Enum0x201枚举
Optional0x202可选类型
ForeignClass0x203外部类,比如CoreFoundation中的类
Opaque0x300在元数据系统中不公开其值的类型
Tuple0x301元祖类型
Function0x302A monomorphic function
Existential0x303An existential type
Metatype0x304A metatype
ObjCClassWrapper0x305An ObjC class wrapper
ExistentialMetatype0x306An existential metatype
HeapLocalVariable0x400使用静态生成的元数据的堆分配的局部变量
HeapGenericLocalVariable0x500使用运行时实例化的元数据的堆分配的局部变量
ErrorObject0x501swift原生的错误类型
LastEnumerated0x7FF最大的非isa指针元数据类型值

所以我们前面type存的是0x200,对应的枚举值是结构体,我们可以从源码的断点处可以看到,我们后面也会用代码实现一遍。

StructMetadata

TargetStructMetadata

StructMetadata是什么呢?我们可以全局搜索下,可以找到

using StructMetadata = TargetStructMetadata<InProcess>;

StructMetadata就是TargetStructMetadata的别名

/// The structure of type metadata for structs.
template <typename Runtime>
struct TargetStructMetadata : public TargetValueMetadata<Runtime> {
  using StoredPointer = typename Runtime::StoredPointer;
  using TargetValueMetadata<Runtime>::TargetValueMetadata;

  const TargetStructDescriptor<Runtime> *getDescription() const {
    return llvm::cast<TargetStructDescriptor<Runtime>>(this->Description);
  }

  ...
};

我们没有找到有什么属性,我们看下他的父类TargetValueMetadata

struct TargetValueMetadata : public TargetMetadata<Runtime> {
  using StoredPointer = typename Runtime::StoredPointer;
  TargetValueMetadata(MetadataKind Kind,
                      const TargetTypeContextDescriptor<Runtime> *description)
      : TargetMetadata<Runtime>(Kind), Description(description) {}

  /// An out-of-line description of the type.
  TargetSignedPointer<Runtime, const TargetValueTypeDescriptor<Runtime> * __ptrauth_swift_type_descriptor> Description;

  ...
};

我们看到初始化方法中有Kinddescription两个属性,但是底下只看到一个属性description,那Kind应该还在父类中,我们继续向上探索父类TargetMetadata

/// Bounds for metadata objects.
struct TargetMetadata {
  using StoredPointer = typename Runtime::StoredPointer;

  /// The basic header type.
  typedef TargetTypeMetadataHeader<Runtime> HeaderType;

  constexpr TargetMetadata()
    : Kind(static_cast<StoredPointer>(MetadataKind::Class)) {}
  constexpr TargetMetadata(MetadataKind Kind)
    : Kind(static_cast<StoredPointer>(Kind)) {}

#if SWIFT_OBJC_INTEROP
protected:
  constexpr TargetMetadata(TargetAnyClassMetadata<Runtime> *isa)
    : Kind(reinterpret_cast<StoredPointer>(isa)) {}
#endif

private:
  /// The kind. Only valid for non-class metadata; getKind() must be used to get
  /// the kind value.
  StoredPointer Kind;
public:
  /// Get the metadata kind.
  MetadataKind getKind() const {
    return getEnumeratedMetadataKind(Kind);
  }
  
  /// Set the metadata kind.
  void setKind(MetadataKind kind) {
    Kind = static_cast<StoredPointer>(kind);
  }

  ...
};

果不其然,我们找到了Kind,不过是StoredPointer类型的,他是Runtime::StoredPointer的别名,那Runtime传进来的是模版,前面我们看到模版传进来的的是InProcess,在InProcess中,我们看到using StoredPointer = uintptr_t;,我们在点开uintptr_t,看到typedef unsigned long uintptr_t;,那本质上Kind就是unsigned long类型。其实我们从上面的代码中就可以看出,这个Kind在与OC的类交互时,传进来的是isa,不然就是MetadataKind

用swift代码简单模拟StructMetadata

上面的代码翻看下来,整个StructMetadata就只有KindDescription2个属性,Kind我们知道了是unsigned long类型,Description暂时还不知道是啥,不过没关系,从名称上来看是一个指针,我们先用一个UnsafeRawPointer来替代他(后面在详细解析他),这样我们可以写如下代码


struct Teacher {
    var age = 18
    var name = "kody"
}

struct StructMetadata {
    var kind: Int
    var Description: UnsafeRawPointer
}

// 通过源码我们可以知道Type类型对应的就是Metadata,这里记住要转成Any.Type,不然typesize不一致,不让转
let ptr = unsafeBitCast(Teacher.self as Any.Type, to: UnsafeMutablePointer<StructMetadata>.self)
print("0x\(String(ptr.pointee.kind, radix: 16))") //0x200

非常棒,输出0x200,和表格里的一致,换成枚举也能得到对应的值,如果强转Teacher?.self,那会得到0x202,这样说明我们的思路可行的。

Swift基础类型底层的详细探索

在上面搜索StructMetadata的时候,细心的小伙伴可以发现下面的代码:

template <typename Runtime> struct TargetGenericMetadataInstantiationCache;
template <typename Runtime> struct TargetAnyClassMetadata;
template <typename Runtime> struct TargetClassMetadata;
template <typename Runtime> struct TargetStructMetadata;
template <typename Runtime> struct TargetOpaqueMetadata;
template <typename Runtime> struct TargetValueMetadata;
template <typename Runtime> struct TargetForeignClassMetadata;
template <typename Runtime> struct TargetContextDescriptor;
template <typename Runtime> class TargetTypeContextDescriptor;
template <typename Runtime> class TargetClassDescriptor;
template <typename Runtime> class TargetValueTypeDescriptor;
template <typename Runtime> class TargetEnumDescriptor;
template <typename Runtime> class TargetStructDescriptor;
template <typename Runtime> struct TargetGenericMetadataPattern;

这里定义了大部分我们想要的底层Metadata了,所以我们只要分析这些类或者结构体,就能得到基础类型的底层结构了。

一开始是想写在一起的,但是感觉太长了,所以会分文章写

待续...