Kealdish's Studio.

反射如何工作

字数统计: 5.5k阅读时长: 22 min
2018/10/22 Share

原文地址 https://swift.org/blog/how-mirror-works/?utm_source=mybridge&utm_medium=blog&utm_campaign=read_more

虽然 swift 是一门特别强调静态类型的语言,但是它同时也支持丰富的元数据类型,也就是说允许在运行时检查代码和操作任意值。这种特性通过 Mirror API 暴露给开发人员。你可能会很好奇,Mirror 是如何在如此强调静态类型的语言中工作的?让我们来一探究竟。

免责声明

接下来所讲得内容是内部实现的细节,所涉及的代码是在当前版本中的写法,在将来可能会发生改变。当 ABI 稳定后,元数据将变成固定和可信赖的格式,但是现在它仍会发生改变。如果你写常规的 swift 代码,不要依赖于元数据。如果你要编写的代码所要实现的是比 Mirror API 所提供的更复杂的反射机制的话,本篇文章将会启发你它们是如何一起配合的,但是要记住所有一切在未来都可能发生改变。

接口

Mirror(reflecting:) 初始化器得参数接受任意值。返回的 Mirror 实例提供关于这个值的信息,主要是它所包含的子项。每个子项包含一个值和一个可选的标签。然后,你就可以在不需要知道编译时的任何类型信息的情况下,使用 Mirror 去遍历一个对象的所有子项。

Mirror 允许类型在遵守 CustomReflectable 协议的条件下,提供自定义的表现。这种特性对于那些希望展示比从内省中获得更多信息的类来说是特别有用的。例如,Array 遵守 CustomReflectable 协议并且将所有的元素暴露为未标签化的子项。 Dictionary 使用这种特性将所有的键值对暴露为标签化的子项。

对于所有其他的类型而言, Mirror 在值的真实内容基础上做了一些魔法操作生成一列子项。对于结构体和类而言,它将存储的属性作为子项展示。对于元组而言,它展示的是元组的元素。枚举则是展示每种情况和其对应的值(如果有的话)。

黑魔法是如何工作的?让我们来看一看。

结构体

反射 API 是由 swift 和 c++ 一起实现的。swift 在实现 swift 式的接口和使得很多任务更容易的方面更合适。更底层的 swift 运行时是由 c++ 实现的,并且从 swift 直接访问那些 c++ 类是不可能的,因此 C 语言层用来连接这两者。swift 层面的实现在 ReflectionMirror.swift , c++ 层面的实现在 ReflectionMirror.mm

这两者通过暴露给 swift 的很小的 c++ 函数集合来相互沟通。它们不用 swift 内置的 C 桥接,而是用一个指令去声明 swift,这个指令会指定一个自定义的符号名称。然后, swift 可以直接调用与该符号名称相关联的 c++ 函数。虽然这个特性允许两者可以直接沟通而不需要担心桥接器背后到底对值做了什么,但是,它要求我们要正确了解 swift 是如何传递参数和返回值的。除非你的代码在运行时需要它,否则不要轻易尝试。

接下来我们来举例说明,我们来看一下 ReflectionMirror.swift 中的 _getChildCount 方法:

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

@_silgen_name 属性告诉 swift 编译器将这个函数映射为 swift_reflectionMirror_count 的符号,而不是常规的 _getChildCount 符号。注意,该属性一开始的下划线表示该属性是由标准库保留。在 c++ 层面,函数看起来像这样:

1
2
3
4
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 代码调用 _getChildCount 时,c++ 函数会被调用,并且会伴随三个参数,一个指向 swift 值指针的 value , 一个包含值的类型参数的 type ,以及类型 T 。

Mirror 中包含的 swift 和 c++ 之间的所有接口如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@_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 并且它的声明如下:

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

passedValue 是一个指向实际传入的 swift 值的指针, T 是该值的静态类型,与 swift 层面的泛型参数 相对应。 passedType 是在 swift 端显示传入的并会被用在真正反射的那一步。当使用父类 Mirror 作为子类的实例时,此类型将与对象的实际运行时类型不同。最后, f 参数是会被用到的,它传入的是该函数查找的实现对象的引用。当该函数被调用时, f 返回的是什么,该函数就会返回什么,这样使得用户更容易获取到返回值。

call 的实现并没有太惊艳的地方。它主要是使用 switch 条件语句以及额外的代码来处理特殊情况。重要的一点是它最终会调用 f 和 ReflectionMirrorImpl 子类的实例,这个实例会调用它的实例方法来完成真正的工作。

下面是 ReflectionMirrorImpl 的结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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 的实现:

1
2
3
4
5
6
7
8
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 来标志其为元组来开始:

1
2
3
4
struct TupleImpl : ReflectionMirrorImpl {
char displayStyle() {
return 't';
}

使用像这样的硬编码常量是不正常的,但是考虑到 c++ 和 swift 都有一个地方引用这个值,并且它们之间不通过桥接来沟通,因而这是个靠谱的选择。

接下来是 count 方法。现在我们已经知道了 type 真正的是 TupleTypeMetadata * 类型而不是 Metadata * 类型。TupleTypeMetadata 有一个 TupleTypeMetadata 字段,该字段保存元组中元素的个数:

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

subscript 方法作用更多一点。它同样使用 static_cast 开始:

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

接下来,通过边界检查来确保调用者访问的索引是元组所包含的:

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

subscriptd 有两个工作:获取值和对应的名称。对于一个结构体或者类而言,名称是存储的属性的名字。对于元组而言,名称是元组某个元素的标签或者是数字索引(如果没有标签的话)。

标签以空格分割存储在元数据的 Labels 字段中。下面的代码是追踪列表中第 i 个字符串:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 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;
}
}

如果没有标签,则生成对应的数字名:

1
2
3
4
5
6
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 来释放,因而下面的代码便是据此来设置:

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

令人惊讶的是值的获取更容易。元组元数据包含一个返回指定索引指向元素的信息的函数:

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

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

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

此外, elt 包含元素的类型。通过该类型和指向该值的指针,使得构建包含该值的新的 Any 成为了可能。类型包含了一些函数指针,这些指针主要用来分配和初始化存储。下面的代码使用那些函数将值拷贝到 Any 中,然后返回它给调用者:

1
2
3
4
5
6
7
8
9
10
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 辅助函数根据给定的类型去查找对应的字段描述符。一旦我们添加了直接引用,这个函数应当消失,然而,同时,它提供了一个有趣的视角来观察运行时代码是如何能够使用语言的元数据来查找类型信息。

函数原型看起来像这样:

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

它包含了检查的类型和要查找的字段索引。它还包含了一个在查找信息时会被触发的回调。

第一个任务是获取该类型的类型上下文描述符,它包含了该类型的额外信息,之后会被用到:

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

这个工作会被拆分为两个部分。首先,它会查找类型的字段描述符。字段描述符包含了该类型的字段的所有信息。一旦字段描述符可以被获取,该方法就可以从描述符中查找到必要的信息。

从描述符中查找信息的功能被封装到叫做 getFieldAt 的辅助函数中。其他代码在搜索适当的字段描述符时可以从各个地方调用。让我们开始搜索。首先是获取一个 demangler ,它的作用是将支离破碎的类型名称转换成实际的类型引用:

1
auto dem = getDemanglerForRuntimeTypeResolution();

它还包含缓存来加速多搜索:

1
auto &cache = FieldCache.get();

如果缓存已经包含了字段描述符,那就调用 getFieldAt

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

为了使搜索的代码更加简单,这里提供一个辅助函数。它包含了 FieldDescriptor 参数,并且来检查它是否是要搜索的那个。如果描述符匹配上了,它将描述符放入到缓存中,调用 getFieldAt 函数,然后给调用者返回成功。匹配是一个复杂的过程,但是可以归结为比较错名:

1
2
3
4
5
6
7
8
9
10
11
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;

字段描述符可以在运行时注册或者在编译时打成二进制数据。这两种方式会循环搜索所有已知的字段描述符来进行匹配:

1
2
3
4
5
6
7
8
9
10
11
12
13
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;
}
}

如果最后没有找到匹配的,那么就打印一个警告,并且触发一个附带空元组的回调。

1
2
3
4
5
6
7
8
9
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 将字段描述符转换成名称和字段类型,然后传递给回调。

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

名称可以直接从记录中获取:

1
auto name = field.getFieldName(0);

如果字段是枚举的情况,它可能不会有类型。先检查它的类型然后触发响应回调:

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

字段记录存储字段类型作为错名。回调期望得到一个指向元数据的指针,因而,错名会被修改为真正的类型。 _getTypeByMangledName 函数处理大部分的工作,但是它要求调用者去处理被该类型所使用到的任意泛型。这要求把类型中嵌套的所有泛型上下文抽出:

1
2
3
4
5
6
7
8
9
10
11
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 传入来解决泛型参数:

1
2
3
4
5
auto typeName = field.getMangledTypeName(0);
auto typeInfo = _getTypeByMangledName(
typeName,
[&](unsigned depth, unsigned index) -> const Metadata * {

如果请求的深度已经超过了描述符路径的大小,那就会失败:

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

否则,从包含字段的类型中获取泛型参数。这要求将索引和深度转换成单一的胖索引,这个工作通过遍历描述符路径和将每个点上的泛型参数个数的值增加,直到给定的路径到达了。

1
2
3
4
5
6
7
8
9
10
11
12
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;
}

如果索引在给定的路径上超过了可获取的泛型参数,那会失败:

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

否则,从基本类型中获取合适的泛型参数:

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

像之前那样,如果类型无法找到,使用空的元组:

1
2
3
4
5
6
7
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());
}

然后,带着找到的类型去触发回调:

1
2
3
4
5
callback(name, FieldType()
.withType(typeInfo)
.withIndirect(field.isIndirectCase())
.withWeak(typeInfo.isWeak()));
};

结构体

结构体的实现是类似的,但更复杂一点。有些结构类型根本不支持反射,在结构体中查找名称和偏移需要花费更多精力,结构体可以包含反射代码需要能够提取的弱引用。

首先有一个辅助函数用来检查该结构体是否支持反射。这被存储在一个 flag 中,可以通过访问结构体元数据来得到它。与上面的元组代码类似,我们知道 type 实际上是 StructMetadata * ,因而我们可以转换:

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

结构体的显示类型为 s :

1
2
3
char displayStyle() {
return 's';
}

子项的数目被元数据所记录的字段的数目,如果该类型不支持反射,则会返回 0 :

1
2
3
4
5
6
7
8
intptr_t count() {
if (!isReflectable()) {
return 0;
}
auto *Struct = static_cast<const StructMetadata *>(type);
return Struct->getDescription()->NumFields;
}

像之前那样, subscript 方法是比较复杂的部分。它的开始很类似,做边界检查然后查找偏移量:

1
2
3
4
5
6
7
8
9
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 函数来完成:

1
2
3
Any result;
_swift_getFieldAt(type, i, [&](llvm::StringRef name, FieldType fieldInfo) {

一旦它有字段信息,接下来的流程和元组类似。填写名称并且计算指向字段存储的指针:

1
2
3
4
5
*outName = name.data();
*outFreeFunc = nullptr;
auto *bytes = reinterpret_cast<char*>(value);
auto *fieldData = reinterpret_cast<OpaqueValue *>(bytes + fieldOffset);

在将字段的值拷贝进 Any 返回值来处理弱引用时还有额外的步骤。 loadSpecialReferenceStorage 函数会处理这些事情。如果它没有加载该值,则该值具有正常存储,并且该值可以正常复制到返回值中。

1
2
3
4
5
6
7
8
9
10
11
12
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-cdebugQuickLookObject 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
#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 运行时获取:

1
2
3
4
5
6
7
8
9
10
11
12
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
}

杂项

在该文件中还有三种实现,大部分都没做什么工作。 ObjCClassImpl 处理 Objective-c 类。它甚至不会尝试返回任何的子项,因为 Objective-c 给 ivar 的内容留下太多的余地。 Objective-c 类允许做一些事情,比如让一个悬垂的指针永远在那里,用一些单独的逻辑告诉实现不要去访问值。尝试返回这样的一个值作为 Mirror 的子项会违反内存安全保证。

MetatypeImpl 处理元类型。如果你在确切的类型上使用 Mirror ,比如 Mirror(reflecting: String.self) ,那就使用它。类似的, OpaqueImpl 处理不透明的类型和返回空。

swift 接口

在 swift 层面, Mirror 调用 c++ 实现的接口函数来获取它需要的信息,然后以友好的形式展现出来。这项工作在 Mirror 的初始化中完成:

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

subjectType 是将用于反映主体值的类型。这通常是值的运行时类型,但如果调用者使用 superclassMirror 来遍历类层次结构,则它将是超类。如果调用者未传入 subjectType ,则此代码要求 C++ 代码获取主体类型:

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

然后通过获取子项的数目来构建 children ,并且创建集合来懒加载每个独立的子项:

1
2
3
4
5
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 的父类。没有父类的非类类型和类不能有父类镜像,因此它们为 nil:

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

调用者可以指定一个自定义祖先表示,它是一个可以作为父类镜像直接返回的 Mirror 实例:

1
2
3
4
5
6
7
8
if let customAncestor = customAncestor {
if superclass == customAncestor.subjectType {
return customAncestor
}
if customAncestor._defaultDescendantRepresentation == .suppressed {
return customAncestor
}
}

否则,以相同的值返回一个新 Mirror ,但使用父类作为 subjectType 的值:

1
2
3
4
return Mirror(internalReflecting: subject,
subjectType: superclass,
customAncestor: customAncestor)
}

最后,它获取并且解析展示类型,并且设置 Mirror 保留的属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
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
}
CATALOG
  1. 1. 免责声明
  2. 2. 接口
  3. 3. 结构体
  4. 4. 奇怪的动态分发
  5. 5. 元组反射
  6. 6. swift_getFieldAt
  7. 7. 结构体
  8. 8.
  9. 9. 杂项
  10. 10. swift 接口