Mirror 工作原理

Swift 非常强调静态类型,但也支持关于类型的丰富元数据,这使得代码能够在运行时检查和操作任意值。这通过 Mirror API 暴露给 Swift 程序员。人们可能会想,像 Mirror 这样的东西在一个如此强调静态类型的语言中是如何工作的?让我们来看一看!

免责声明

这里的一切都是内部实现细节。代码是截至本文撰写时的最新版本,但可能会发生变化。当 ABI 稳定性实现时,元数据将成为固定的、可靠的格式,但目前这仍然可能发生变化。如果您正在编写普通的 Swift 代码,请不要依赖这里的任何内容。如果您正在编写的代码想要执行比 Mirror 提供的更复杂的反射,这将为您提供一个起点,但您需要使其与更改保持同步,直到 ABI 稳定。如果您想研究 Mirror 代码本身,这应该让您对它的整体结构有一个很好的了解,但请记住,事情可能会发生变化。

接口

Mirror(reflecting:) 初始化器接受任意值。生成的 Mirror 实例随后提供关于该值的信息,主要是它包含的子项。一个子项包含一个值和一个可选的标签。然后您可以在子值上使用 Mirror 来遍历整个对象图,而无需在编译时知道任何类型。

Mirror 允许类型通过遵循 CustomReflectable 协议来提供自定义表示。这对于想要呈现比从内省获得的更友好的类型的类型很有用。例如,Array 遵循 CustomReflectable 并将数组的元素作为未标记的子项公开。Dictionary 使用它来将其键/值对作为标记的子项公开。

对于所有其他类型,Mirror 执行一些魔法,以根据值的实际内容生成子项列表。对于结构体和类,它将存储的属性呈现为子项。对于元组,它呈现元组元素。枚举呈现枚举用例和关联值(如果存在)。

这种魔法是如何工作的?让我们来 выяснить!

结构

反射 API 部分在 Swift 中实现,部分在 C++ 中实现。Swift 更适合实现 Swifty 接口,并使许多任务更轻松。Swift 运行时的底层级别在 C++ 中实现,并且无法直接从 Swift 访问这些 C++ 类,因此 C 层连接了两者。Swift 侧在 ReflectionMirror.swift 中实现,C++ 侧在 ReflectionMirror.mm 中实现。

这两部分通过一小组暴露给 Swift 的 C++ 函数进行通信。它们不是使用 Swift 内置的 C 桥接,而是在 Swift 中使用指令声明,该指令指定自定义符号名称,然后精心制作具有该名称的 C++ 函数,以便可以直接从 Swift 调用。这允许两部分直接通信,而无需担心桥接机制将在幕后对值执行的操作,但这需要确切了解 Swift 如何传递参数和返回值。除非您正在处理需要它的运行时代码,否则不要在家里尝试这样做。

例如,请查看 ReflectionMirror.swift 中的 _getChildCount 函数

@_silgen_name("swift_reflectionMirror_count")
internal func _getChildCount<T>(_: T, type: Any.Type) -> Int

@_silgen_name 属性通知 Swift 编译器将此函数映射到名为 swift_reflectionMirror_count 的符号,而不是应用于 _getChildCount 的通常的 Swift 命名修饰。请注意,开头的下划线表示此属性是为标准库保留的。在 C++ 侧,该函数如下所示

SWIFT_CC(swift) SWIFT_RUNTIME_STDLIB_INTERFACE
intptr_t swift_reflectionMirror_count(OpaqueValue *value,
                                      const Metadata *type,
                                      const Metadata *T) {

SWIFT_CC(swift) 告诉编译器此函数使用 Swift 调用约定而不是 C/C++ 约定。SWIFT_RUNTIME_STDLIB_INTERFACE 将其标记为 Swift 侧接口的一部分的函数,并具有将其标记为 extern "C" 的效果,这避免了 C++ 名称修饰并确保此函数将具有 Swift 侧期望的符号名称。C++ 参数经过精心安排,以匹配 Swift 基于 Swift 声明调用此函数的方式。当 Swift 代码调用 _getChildCount 时,将使用 value 调用 C++ 函数,其中 value 包含指向 Swift 值的指针,type 包含类型参数的值,T 包含与泛型 <T> 对应的类型。

Mirror 的 Swift 和 C++ 部分之间的完整接口包含以下函数

@_silgen_name("swift_reflectionMirror_normalizedType")
internal func _getNormalizedType<T>(_: T, type: Any.Type) -> Any.Type

@_silgen_name("swift_reflectionMirror_count")
internal func _getChildCount<T>(_: T, type: Any.Type) -> Int

internal typealias NameFreeFunc = @convention(c) (UnsafePointer<CChar>?) -> Void

@_silgen_name("swift_reflectionMirror_subscript")
internal func _getChild<T>(
  of: T,
  type: Any.Type,
  index: Int,
  outName: UnsafeMutablePointer<UnsafePointer<CChar>?>,
  outFreeFunc: UnsafeMutablePointer<NameFreeFunc?>
) -> Any

// Returns 'c' (class), 'e' (enum), 's' (struct), 't' (tuple), or '\0' (none)
@_silgen_name("swift_reflectionMirror_displayStyle")
internal func _getDisplayStyle<T>(_: T) -> CChar

@_silgen_name("swift_reflectionMirror_quickLookObject")
internal func _getQuickLookObject<T>(_: T) -> AnyObject?

@_silgen_name("_swift_stdlib_NSObject_isKindOfClass")
internal func _isImpl(_ object: AnyObject, kindOf: AnyObject) -> Bool

以奇怪的方式完成动态派发

没有一种通用的方法可以从任何类型中获取我们想要的信息。元组、结构体、类和枚举都需要不同的代码来执行许多这些任务,例如查找子项的数量。还有更细微之处,例如对 Swift 和 Objective-C 类的不同处理。

所有这些函数都需要代码,这些代码根据正在检查的类型种类分派到不同的实现。这听起来很像方法的动态派发,只是选择要调用的实现比检查方法正在使用的对象的类更复杂。反射代码尝试通过使用 C++ 动态派发来简化问题,该动态派发具有包含上述接口的 C++ 版本和一个涵盖所有各种情况的大量子类的抽象基类。单个函数将 Swift 类型映射到其中一个 C++ 类的实例。然后,在该实例上调用方法会分派到适当的实现。

映射函数称为 call,其声明如下所示

template<typename F>
auto call(OpaqueValue *passedValue, const Metadata *T, const Metadata *passedType,
          const F &f) -> decltype(f(nullptr))

passedValue 是指向传入的实际 Swift 值的指针。T 是该值的静态类型,它对应于 Swift 侧的泛型参数 <T>passedType 是 Swift 侧显式传入的类型,用于实际的反射步骤。(当使用子类的实例的超类 Mirror 时,此类型将与对象的实际运行时类型不同。)最后,f 参数是将被调用的东西,传入对此函数查找的实现对象的引用。然后,此函数返回调用 f 时返回的任何内容,以便用户更容易取回值。

call 的实现并没有太令人兴奋。它主要是带有用于处理特殊情况的一些额外代码的 switch 语句。重要的是,它最终将使用 ReflectionMirrorImpl 的子类的实例调用 f,然后它将调用该实例上的方法来完成实际工作。

这是 ReflectionMirrorImpl,它是所有内容都通过的接口

struct ReflectionMirrorImpl {
  const Metadata *type;
  OpaqueValue *value;

  virtual char displayStyle() = 0;
  virtual intptr_t count() = 0;
  virtual AnyReturn subscript(intptr_t index, const char **outName,
                              void (**outFreeFunc)(const char *)) = 0;
  virtual const char *enumCaseName() { return nullptr; }

#if SWIFT_OBJC_INTEROP
  virtual id quickLookObject() { return nil; }
#endif

  virtual ~ReflectionMirrorImpl() {}
};

充当 Swift 和 C++ 组件之间接口的函数然后使用 call 来调用相应的方法。例如,这是 swift_reflectionMirror_count 的样子

SWIFT_CC(swift) SWIFT_RUNTIME_STDLIB_INTERFACE
intptr_t swift_reflectionMirror_count(OpaqueValue *value,
                                      const Metadata *type,
                                      const Metadata *T) {
  return call(value, T, type, [](ReflectionMirrorImpl *impl) {
    return impl->count();
  });
}

元组反射

让我们从元组反射开始,这可能是最简单的,但仍然完成了一些工作。它首先返回一个显示样式 't',以指示它是一个元组

struct TupleImpl : ReflectionMirrorImpl {
  char displayStyle() {
    return 't';
  }

像这样使用硬编码常量是不寻常的,但考虑到在 C++ 中只有一个地方,在 Swift 中只有一个地方引用此值,并且它们不使用桥接进行通信,这是一个合理的选择。

接下来是 count 方法。此时我们知道 type 实际上是一个 TupleTypeMetadata * 而不仅仅是一个 Metadata *TupleTypeMetadata 有一个 NumElements 字段,其中保存了元组中元素的数量,我们就完成了

  intptr_t count() {
    auto *Tuple = static_cast<const TupleTypeMetadata *>(type);
    return Tuple->NumElements;
  }

subscript 方法需要做更多的工作。它以相同的 static_cast 开始

  AnyReturn subscript(intptr_t i, const char **outName,
                      void (**outFreeFunc)(const char *)) {
    auto *Tuple = static_cast<const TupleTypeMetadata *>(type);

接下来,进行边界检查,以确保调用者没有请求此元组无法包含的索引

    if (i < 0 || (size_t)i > Tuple->NumElements)
      swift::crash("Swift mirror subscript bounds check failure");

Subscript 有两个任务:它检索值和相应的名称。对于结构体或类,名称是存储的属性的名称。对于元组,名称是该元素的元组标签,或者如果没有标签,则是一个数字指示符,如 .0

标签存储在元数据的 Labels 字段中以空格分隔的列表中。此代码跟踪该列表中的第 i 个字符串

    // Determine whether there is a label.
    bool hasLabel = false;
    if (const char *labels = Tuple->Labels) {
      const char *space = strchr(labels, ' ');
      for (intptr_t j = 0; j != i && space; ++j) {
        labels = space + 1;
        space = strchr(labels, ' ');
      }

      // If we have a label, create it.
      if (labels && space && labels != space) {
        *outName = strndup(labels, space - labels);
        hasLabel = true;
      }
    }

如果没有标签,则生成适当的数字名称

    if (!hasLabel) {
      // The name is the stringized element number '.0'.
      char *str;
      asprintf(&str, ".%" PRIdPTR, i);
      *outName = str;
    }

因为我们正在 Swift 和 C++ 的交集处工作,所以我们没有自动内存管理等好东西。Swift 有 ARC,C++ 有 RAII,但两者不能很好地相处。outFreeFunc 允许 C++ 代码向调用者提供一个函数,它将使用该函数来释放返回的名称。标签需要使用 free 释放,因此此代码相应地设置 *outFreeFunc 的值

    *outFreeFunc = [](const char *str) { free(const_cast<char *>(str)); };

这处理了名称。令人惊讶的是,值的检索更简单。Tuple 元数据包含一个函数,该函数返回有关给定索引处的元素的信息

    auto &elt = Tuple->getElement(i);

elt 包含一个偏移量,该偏移量可以应用于元组值以获取指向元素值的指针

    auto *bytes = reinterpret_cast<const char *>(value);
    auto *eltData = reinterpret_cast<const OpaqueValue *>(bytes + elt.Offset);

elt 还包含元素的类型。使用类型和指向值的指针,可以构造一个新的 Any,其中包含该值。该类型包含用于分配和初始化包含给定类型值的存储的功能指针。此代码使用这些函数将值复制到 Any 中,然后将 Any 返回给调用者

    Any result;

    result.Type = elt.Type;
    auto *opaqueValueAddr = result.Type->allocateBoxForExistentialIn(&result.Buffer);
    result.Type->vw_initializeWithCopy(opaqueValueAddr,
                                       const_cast<OpaqueValue *>(eltData));

    return AnyReturn(result);
  }
};

元组就是这样。

swift_getFieldAt

在结构体、类和枚举中查找元素当前非常复杂。这种复杂性的大部分是由于这些类型与字段描述符之间缺少直接引用,字段描述符包含有关类型字段的信息。一个名为 swift_getFieldAt 的辅助函数搜索给定类型的适当字段描述符。一旦我们添加了直接引用,整个函数应该消失,但在此期间,它提供了一个有趣的视角,了解运行时代码如何使用语言的元数据来查找类型信息。

函数原型如下所示

void swift::_swift_getFieldAt(
    const Metadata *base, unsigned index,
    std::function<void(llvm::StringRef name, FieldType fieldInfo)>
        callback) {

它接受要检查的类型和要查找的字段索引。它还接受一个回调,将在其中调用查找的信息。

首要任务是获取此类型的类型上下文描述符,其中包含有关该类型的其他信息,稍后将使用这些信息

  auto *baseDesc = base->getTypeContextDescriptor();
  if (!baseDesc)
    return;

工作分为两部分。首先,它查找类型的字段描述符。字段描述符包含有关类型字段的所有信息。一旦字段描述符可用,此函数就可以从描述符中查找必要的信息。

从描述符中查找信息包装在一个名为 getFieldAt 的辅助函数中,其他代码从其搜索适当字段描述符的各个位置调用该函数。让我们从搜索开始。它首先获取一个解构器,该解构器用于将已命名修饰的类型名称转换为实际的类型引用

  auto dem = getDemanglerForRuntimeTypeResolution();

它还有一个缓存来加速多次搜索

  auto &cache = FieldCache.get();

如果缓存已经具有字段描述符,则使用它调用 getFieldAt

  if (auto Value = cache.FieldCache.find(base)) {
    getFieldAt(*Value->getDescription());
    return;
  }

为了使搜索代码更简单,有一个辅助函数,它接受 FieldDescriptor 并检查它是否是正在搜索的那个。如果描述符匹配,它会将描述符放在缓存中,调用 getFieldAt,并向调用者返回成功。匹配很复杂,但本质上归结为比较命名修饰的名称

  auto isRequestedDescriptor = [&](const FieldDescriptor &descriptor) {
    assert(descriptor.hasMangledTypeName());
    auto mangledName = descriptor.getMangledTypeName(0);

    if (!_contextDescriptorMatchesMangling(baseDesc,
                                           dem.demangleType(mangledName)))
      return false;

    cache.FieldCache.getOrInsert(base, &descriptor);
    getFieldAt(descriptor);
    return true;
  };

字段描述符可以在运行时注册,也可以在构建时烘焙到二进制文件中。这两个循环搜索所有已知的字段描述符以查找匹配项

  for (auto &section : cache.DynamicSections.snapshot()) {
    for (const auto *descriptor : section) {
      if (isRequestedDescriptor(*descriptor))
        return;
    }
  }

  for (const auto &section : cache.StaticSections.snapshot()) {
    for (auto &descriptor : section) {
      if (isRequestedDescriptor(descriptor))
        return;
    }
  }

如果未找到匹配项,则记录警告并使用空元组调用回调,只是为了给它一些东西

  auto typeName = swift_getTypeName(base, /*qualified*/ true);
  warning(0, "SWIFT RUNTIME BUG: unable to find field metadata for type '%*s'\n",
             (int)typeName.length, typeName.data);
  callback("unknown",
           FieldType()
             .withType(TypeInfo(&METADATA_SYM(EMPTY_TUPLE_MANGLING), {}))
             .withIndirect(false)
             .withWeak(false));
}

这处理了字段描述符的搜索。getFieldAt 辅助函数将字段描述符转换为传递给回调的名称和字段类型。它首先从字段描述符中获取请求的字段记录

  auto getFieldAt = [&](const FieldDescriptor &descriptor) {
    auto &field = descriptor.getFields()[index];

名称可以直接从记录中访问

    auto name = field.getFieldName(0);

如果该字段实际上是一个枚举用例,则它可能没有类型。尽早检查并相应地调用回调

    if (!field.hasMangledTypeName()) {
      callback(name, FieldType().withIndirect(field.isIndirectCase()));
      return;
    }

字段记录将字段类型存储为命名修饰的名称。回调期望指向元数据的指针,因此命名修饰的名称必须解析为实际类型。函数 _getTypeByMangledName 处理了大部分工作,但要求调用者解析类型使用的任何泛型参数。执行此操作需要提取类型嵌套在其中的所有泛型上下文

    std::vector<const ContextDescriptor *> descriptorPath;
    {
      const auto *parent = reinterpret_cast<
                              const ContextDescriptor *>(baseDesc);
      while (parent) {
        if (parent->isGeneric())
          descriptorPath.push_back(parent);

        parent = parent->Parent.get();
      }
    }

现在获取命名修饰的名称并获取类型,传入一个 lambda,该 lambda 解析泛型参数

    auto typeName = field.getMangledTypeName(0);

    auto typeInfo = _getTypeByMangledName(
        typeName,
        [&](unsigned depth, unsigned index) -> const Metadata * {

如果请求的深度超出描述符路径的大小,则失败

          if (depth >= descriptorPath.size())
            return nullptr;

否则,从包含该字段的类型中获取泛型参数。这需要将索引和深度转换为单个平面索引,这是通过向上遍历描述符路径并在每个阶段添加泛型参数的数量直到达到给定深度来完成的

          unsigned currentDepth = 0;
          unsigned flatIndex = index;
          const ContextDescriptor *currentContext = descriptorPath.back();

          for (const auto *context : llvm::reverse(descriptorPath)) {
            if (currentDepth >= depth)
              break;

            flatIndex += context->getNumGenericParams();
            currentContext = context;
            ++currentDepth;
          }

如果索引超出给定深度可用的泛型参数,则失败

          if (index >= currentContext->getNumGenericParams())
            return nullptr;

否则,从基类型获取适当的泛型参数

          return base->getGenericArgs()[flatIndex];
        });

和以前一样,如果找不到该类型,则使用空元组

    if (typeInfo == nullptr) {
      typeInfo = TypeInfo(&METADATA_SYM(EMPTY_TUPLE_MANGLING), {});
      warning(0, "SWIFT RUNTIME BUG: unable to demangle type of field '%*s'. "
                 "mangled type name is '%*s'\n",
                 (int)name.size(), name.data(),
                 (int)typeName.size(), typeName.data());
    }

然后使用找到的任何内容调用回调

    callback(name, FieldType()
                       .withType(typeInfo)
                       .withIndirect(field.isIndirectCase())
                       .withWeak(typeInfo.isWeak()));

  };

这就是 swift_getFieldAt。有了这个辅助函数,让我们看一下其他反射实现。

结构体

结构体的实现类似,但稍微复杂一些。有些结构体类型根本不支持反射,在结构体中查找名称和偏移量需要付出更多努力,并且结构体可以包含反射代码需要能够提取的弱引用。

首先是一个辅助方法,用于检查结构体是否可以反射。这存储在一个标志中,该标志可以通过结构体元数据访问。与上面的元组代码类似,我们此时知道 type 实际上是一个 StructMetadata *,因此我们可以自由地转换

struct StructImpl : ReflectionMirrorImpl {
  bool isReflectable() {
    const auto *Struct = static_cast<const StructMetadata *>(type);
    const auto &Description = Struct->getDescription();
    return Description->getTypeContextDescriptorFlags().isReflectable();
  }

结构体的显示样式为 's'

  char displayStyle() {
    return 's';
  }

子项计数是元数据报告的字段数,或者如果此类型实际上不可反射,则为 0

  intptr_t count() {
    if (!isReflectable()) {
      return 0;
    }

    auto *Struct = static_cast<const StructMetadata *>(type);
    return Struct->getDescription()->NumFields;
  }

和以前一样,subscript 方法是复杂的部分。它以类似的方式开始,执行边界检查并查找偏移量

  AnyReturn subscript(intptr_t i, const char **outName,
                      void (**outFreeFunc)(const char *)) {
    auto *Struct = static_cast<const StructMetadata *>(type);

    if (i < 0 || (size_t)i > Struct->getDescription()->NumFields)
      swift::crash("Swift mirror subscript bounds check failure");

    // Load the offset from its respective vector.
    auto fieldOffset = Struct->getFieldOffsets()[i];

获取结构体字段的类型信息有点复杂。这项工作传递给 _swift_getFieldAt 辅助函数

    Any result;

    _swift_getFieldAt(type, i, [&](llvm::StringRef name, FieldType fieldInfo) {

一旦它有了字段信息,事情就会像元组代码一样进行。填写名称并计算指向字段存储的指针

      *outName = name.data();
      *outFreeFunc = nullptr;

      auto *bytes = reinterpret_cast<char*>(value);
      auto *fieldData = reinterpret_cast<OpaqueValue *>(bytes + fieldOffset);

有一个额外的步骤,将字段的值复制到 Any 返回值中,以处理弱引用。loadSpecialReferenceStorage 函数处理这些。如果它没有加载值,则该值具有正常存储,并且该值可以正常复制到返回值中

      bool didLoad = loadSpecialReferenceStorage(fieldData, fieldInfo, &result);
      if (!didLoad) {
        result.Type = fieldInfo.getType();
        auto *opaqueValueAddr = result.Type->allocateBoxForExistentialIn(&result.Buffer);
        result.Type->vw_initializeWithCopy(opaqueValueAddr,
                                           const_cast<OpaqueValue *>(fieldData));
      }
    });

    return AnyReturn(result);
  }
};

结构体就是这样。

类与结构体类似,并且 ClassImpl 中的代码几乎相同。由于 Objective-C 互操作性,有两个值得注意的区别。一个是它实现了 quickLookObject,它会调用 Objective-C 的 debugQuickLookObject 方法

#if SWIFT_OBJC_INTEROP
id quickLookObject() {
  id object = [*reinterpret_cast<const id *>(value) retain];
  if ([object respondsToSelector:@selector(debugQuickLookObject)]) {
    id quickLookObject = [object debugQuickLookObject];
    [quickLookObject retain];
    [object release];
    return quickLookObject;
  }

  return object;
}
#endif

另一个区别是,如果类具有 Objective-C 超类,则必须从 Objective-C 运行时获取字段偏移量

  uintptr_t fieldOffset;
  if (usesNativeSwiftReferenceCounting(Clas)) {
    fieldOffset = Clas->getFieldOffsets()[i];
  } else {
#if SWIFT_OBJC_INTEROP
    Ivar *ivars = class_copyIvarList((Class)Clas, nullptr);
    fieldOffset = ivar_getOffset(ivars[i]);
    free(ivars);
#else
    swift::crash("Object appears to be Objective-C, but no runtime.");
#endif
  }

枚举

枚举有点不同。Mirror 认为一个枚举实例最多只有一个子项,该子项以枚举用例名称作为其标签,并以关联值作为其值。没有关联值的用例没有子项。例如

enum Foo {
  case bar
  case baz(Int)
  case quux(String, String)
}

当在 Foo 的值上使用 mirror 时,对于 Foo.bar 将不显示子项,对于 Foo.baz 将显示一个带有 Int 值的子项,对于 Foo.quux 将显示一个带有 (String, String) 值的子项。虽然类或结构体的值始终包含相同的字段,因此具有相同的子标签和类型,但同一类型的不同枚举用例则不然。关联值也可以是 indirect,这需要特殊处理。

要反射一个 enum 值,需要四个关键信息:用例名称、标签(表示值存储哪个枚举用例的数字表示形式)、有效负载类型以及有效负载是否是间接的。getInfo 方法获取所有这些值

const char *getInfo(unsigned *tagPtr = nullptr,
                    const Metadata **payloadTypePtr = nullptr,
                    bool *indirectPtr = nullptr) {

标签是通过直接查询元数据来检索的

  unsigned tag = type->vw_getEnumTag(value);

其他信息是使用 _swift_getFieldAt 检索的。它将标签作为“字段索引”,并提供适当的信息

  const Metadata *payloadType = nullptr;
  bool indirect = false;

  const char *caseName = nullptr;
  _swift_getFieldAt(type, tag, [&](llvm::StringRef name, FieldType info) {
    caseName = name.data();
    payloadType = info.getType();
    indirect = info.isIndirect();
  });

然后,所有这些值都会返回给调用方

  if (tagPtr)
    *tagPtr = tag;
  if (payloadTypePtr)
    *payloadTypePtr = payloadType;
  if (indirectPtr)
    *indirectPtr = indirect;

  return caseName;
}

(您可能会想:为什么用例名称是直接返回的,而其他三个是通过指针返回的?为什么不返回标签或有效负载类型?答案是:我真的不知道,当时看起来是个好主意。)

count 方法然后可以使用 getInfo 来检索有效负载类型,如果有效负载类型为 null,则返回 0,否则返回 1

intptr_t count() {
  if (!isReflectable()) {
    return 0;
  }

  const Metadata *payloadType;
  getInfo(nullptr, &payloadType, nullptr);
  return (payloadType != nullptr) ? 1 : 0;
}

subscript 方法首先获取有关值的所有信息

AnyReturn subscript(intptr_t i, const char **outName,
                    void (**outFreeFunc)(const char *)) {
  unsigned tag;
  const Metadata *payloadType;
  bool indirect;

  auto *caseName = getInfo(&tag, &payloadType, &indirect);

实际复制值需要更多的工作。为了处理间接值,整个过程需要经过一个额外的 box

  const Metadata *boxType = (indirect ? &METADATA_SYM(Bo).base : payloadType);
  BoxPair pair = swift_allocBox(boxType);

由于枚举提取的工作方式,无法干净地复制值。唯一可用的操作是破坏性地提取有效负载值。为了制作副本并保持原始值不变,破坏性地提取它,然后将其放回原处

  type->vw_destructiveProjectEnumData(const_cast<OpaqueValue *>(value));
  boxType->vw_initializeWithCopy(pair.buffer, const_cast<OpaqueValue *>(value));
  type->vw_destructiveInjectEnumTag(const_cast<OpaqueValue *>(value), tag);

  value = pair.buffer;

在间接情况下,必须从 box 中拉出真实数据

  if (indirect) {
    const HeapObject *owner = *reinterpret_cast<HeapObject * const *>(value);
    value = swift_projectBox(const_cast<HeapObject *>(owner));
  }

一切都已就绪。子项的标签设置为用例名称

  *outName = caseName;
  *outFreeFunc = nullptr;

现在使用熟悉的模式将有效负载作为 Any 返回

  Any result;

  result.Type = payloadType;
  auto *opaqueValueAddr = result.Type->allocateBoxForExistentialIn(&result.Buffer);
  result.Type->vw_initializeWithCopy(opaqueValueAddr,
                                     const_cast<OpaqueValue *>(value));

  swift_release(pair.object);
  return AnyReturn(result);
}

其他种类

此文件中还有三个实现,它们几乎什么都不做。ObjCClassImpl 处理 Objective-C 类。它甚至不尝试为这些类返回任何子项,因为 Objective-C 允许对 ivar 的内容进行过多的自由处理。Objective-C 类允许执行诸如永远保留悬空指针之类的操作,并使用一些单独的逻辑来告诉实现不要接触该值。尝试将这样的值作为 Mirror 的子项返回将违反 Swift 的内存安全保证。没有可靠的方法来判断所讨论的值是否正在执行此类操作,因此此代码完全避免了这种情况。

MetatypeImpl 处理元类型。如果在实际类型(例如 Mirror(reflecting: String.self))上使用 Mirror,则会使用它。这里可能有一些有用的信息可以提供,但目前它甚至没有尝试,只是什么都不返回。同样,OpaqueImpl 处理不透明类型,并且不返回任何内容。

Swift 接口

在 Swift 方面,Mirror 调用 C++ 中实现的接口函数来检索所需的信息,然后以更友好的形式呈现它。这在 Mirror 的初始化器中完成

internal init(internalReflecting subject: Any,
            subjectType: Any.Type? = nil,
            customAncestor: Mirror? = nil)
{

subjectType 是将用于反射 subject 值的类型。这通常是值的运行时类型,但如果调用方使用 superclassMirror 来向上遍历类层次结构,则它将是超类。如果调用方没有传入 subjectType,则此代码会要求 C++ 代码获取 subject 的类型

  let subjectType = subjectType ?? _getNormalizedType(subject, type: type(of: subject))

然后,它通过获取子项的数量并创建一个延迟获取每个子项的集合来构造 children

  let childCount = _getChildCount(subject, type: subjectType)
  let children = (0 ..< childCount).lazy.map({
    getChild(of: subject, type: subjectType, index: $0)
  })
  self.children = Children(children)

getChild 函数是 C++ _getChild 函数的一个小型包装器,它将包含标签名称的 C 字符串转换为 Swift String

Mirror 具有 superclassMirror 属性,该属性返回一个 Mirror,用于检查类层次结构中上一级的类的属性。在内部,它具有一个 _makeSuperclassMirror 属性,该属性存储一个闭包,该闭包可以按需构造超类 Mirror。该闭包首先获取 subjectType 的超类。非类类型和没有超类的类不能具有超类 mirror,因此它们获得 nil

  self._makeSuperclassMirror = {
    guard let subjectClass = subjectType as? AnyClass,
          let superclass = _getSuperclass(subjectClass) else {
      return nil
    }

调用方可以指定自定义祖先表示形式,这是一个可以直接作为超类 mirror 返回的 Mirror 实例

    if let customAncestor = customAncestor {
      if superclass == customAncestor.subjectType {
        return customAncestor
      }
      if customAncestor._defaultDescendantRepresentation == .suppressed {
        return customAncestor
      }
    }

否则,为相同的值返回一个新的 Mirror,但使用 superclass 作为 subjectType

    return Mirror(internalReflecting: subject,
                  subjectType: superclass,
                  customAncestor: customAncestor)
  }

最后,它获取并解码显示样式,并设置 Mirror 的其余属性

    let rawDisplayStyle = _getDisplayStyle(subject)
    switch UnicodeScalar(Int(rawDisplayStyle)) {
    case "c": self.displayStyle = .class
    case "e": self.displayStyle = .enum
    case "s": self.displayStyle = .struct
    case "t": self.displayStyle = .tuple
    case "\0": self.displayStyle = nil
    default: preconditionFailure("Unknown raw display style '\(rawDisplayStyle)'")
    }

    self.subjectType = subjectType
    self._defaultDescendantRepresentation = .generated
  }

结论

Swift 丰富的类型元数据主要存在于幕后,支持协议一致性查找和泛型类型解析等功能。其中一部分通过 Mirror 类型暴露给用户,允许在运行时检查任意值。考虑到 Swift 的静态类型特性,乍一看可能显得奇怪而神秘,但它实际上是对已可用信息的直接应用。对实现的这次巡览应该有助于消除这种神秘感,并让您深入了解在使用 Mirror 时发生的事情。