做者:Mike Ash,原文连接,原文日期:2018-09-26 译者:Nemocdz;校对:numbbbbb,小铁匠Linus;定稿:Forelaxc++
尽管 Swift 重心在强调静态类型上,但它同时支持丰富的元数据类型。元数据类型容许代码在运行时检查和操做任意值。这个功能经过 Mirror
API 暴露给 Swift 开发者。你们可能会感到困惑,在 Swift 这种如此强调静态类型的语言里,Mirror
这样的特性是怎么工做的?让咱们一块儿来经过这篇文章了解一下。git
这里介绍的东西都是内部实现的细节。这些代码的版本是写下文章时的版本,代码可能会随着版本改变。元数据会随着 ABI 稳定的到来而变得稳定和可靠,但在到来那时也会容易发生变化。你们在写平常的 Swift 代码时,不要依赖这里讲的一切。若是你想作比 Mirror
所提供的方式更复杂的反射,这里会给你一些思路。但在 ABI 的稳定前,还须要保持相关变化的关注。若是你想使用 Mirror
自己,这篇文章会提供一些好的思路去作接入和适配。不过再次提醒,这些东西可能会随着版本而改变。github
Mirror(reflecting:)
初始化方法能够接受任意值,返回结果是一个提供该值子元素集合 Children
的相关信息的实例。一个 Child
由可选的标签和值构成。能够在编译期且不用知道任何类型信息状况下,在 Child
的值上用 Mirror
去遍历整个对象的层级视图。 Mirror
容许类型用遵循 CustomReflectable
协议的方式提供一个自定义的表示方式。这给那些想表示得比内建形式更友好的类型提供一种有效的方法。 好比 Array
类型遵照 CustomReflectable
协议而且暴露其中的元素为无标签的 Children
。Dictionary
使用这种方法暴露其中的键值对为带标签的 Children
。 对于其余类型,Mirror
用魔法去返回一个基于其中的实际子元素的 Children
集合。对于结构体和类,Children
为其中储存的属性值。对于元组,Children
为元组的子元素。枚举则是枚举的 case 和其关联的值(若是有的话)。 这些神奇的魔法是怎么工做的呢?让咱们一块儿来了解一下。swift
反射的 API 有一部分是用 Swift 实现的,另外一部分是用 C++ 实现的。Swift 更适合用在实现更 Swift 的接口,并让不少任务变得更简单。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
符号,而不是 Swift 一般对应到的 _getChildCount
方法名修饰。须要注意的是,最前面的下划线表示这个修饰符是被保留在标准库中的。在 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 调用 _getChildCount
时,C++ 会用包含的 Swift 值指针的 value
,包含类型参数的 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
复制代码
没有一种单1、通用的方式去获取任意类型中咱们想要的信息。元组、结构、类和枚举都须要不一样的代码去完成这些繁多的任务,好比说查找子元素的数量。其中还有一些更深、微妙的不一样之处,好比对 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
,接口的全部东西都要传入:app
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");
复制代码
下标有两个做用:能够检索元素和对应的名字。对于一个结构体或者类来讲,这个名字就是所储存的属性名。而对于元组来讲,这个名字要么是该元素的元组标签,要么在没有标签的状况下就是一个相似 .0
的数值指示器。 标签以一个用空格作间隔的列表存储,放在元数据的 Labels
字段中。这段代码查找列表中的第 i 个字符串:
// 肯定是否有一个标签
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++ 有 RALL, 可是这两种技术没办法兼容。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_getField
的帮助函数能够查找给定类型相应的字段描述符。一但咱们添加了那个直接的引用,这整个函数应该就没啥做用了,但在同一时刻,它提供了运行时代码怎么能作到用语言的元数据去查找类型信息的一个有趣思路。 这个函数原型是这样的:
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 §ion : cache.DynamicSections.snapshot()) {
for (const auto *descriptor : section) {
if (isRequestedDescriptor(*descriptor))
return;
}
}
for (const auto §ion : 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 表达式来解决范型参数:
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
会考虑一个枚举实例最多只包含一个元素,枚举 case 名字做为标签,它的关联值做为值。没有关联值的 case 没有包含的元素。 举个例子:
enum Foo {
case bar
case baz(Int)
case quux(String, String)
}
复制代码
当 Foo
类型的值使用 mirror 时,mirror 会显示 Foo.bar
没有子元素,Foo.baz
有一个 Int
类型的元素,Foo.quux
有一个 (String, String)
类型的元素。相同的子标签和类型的类和结构体的值有着相同字段,但同一个类型的不一样的枚举 case 不是这样的。关联的值也多是间接的,因此须要一些特殊处理。 enum
的反射须要四部分核心的信息:case 的名字,tag(表示该值储存的枚举 case 的数字),payload 的类型,是不是间接的 payload。getInfo
方法获取这些值:
const char *getInfo(unsigned *tagPtr = nullptr, const Metadata **payloadTypePtr = nullptr, bool *indirectPtr = nullptr) {
复制代码
tag 从请求元数据直接检索而来:
unsigned tag = type->vw_getEnumTag(value);
复制代码
其它信息用 _swift_getFieldAt
检索而来。将 tag 做为字段索引来调用,就会提供合适的信息:
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;
}
复制代码
(你可能会好奇:为何只有 case 的名字是直接返回的,而其它的三个信息用指针返回?为何不返回 tag 或者 payload 的类型?答案是:我真的不知道,可能在那个时机看起来是个好主意) count
方法能够用 getInfo
方法去检索 payload 的类型,并返回 0 或 1 表示 payload 类型是否为 null:
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);
复制代码
间接的状况下,真实值要在 box 中取出:
if (indirect) {
const HeapObject *owner = *reinterpret_cast<HeapObject * const *>(value);
value = swift_projectBox(const_cast<HeapObject *>(owner));
}
复制代码
如今一切都准备好了。给 case 名字设置子标签:
*outName = caseName;
*outFreeFunc = nullptr;
复制代码
似曾相识的方式被用在将 payload 返回为 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 在 ivars 的内容上容许太多种补救方案了。Objective-C 的类容许保持野指针一直存在,并须要单独的逻辑让实现不要去碰那个值。由于这样的值尝试做为 Mirror
子元素返回,会违反 Swift 的安全性保证。由于没有办法可靠地去告知应该如何处理若是值出了问题,因此代码避开处理整个这种状况。 MetatypeImpl
处理元类型。若是将 Mirror
用在实际的类型,好比这样用 Mirror(reflecting:String.self)
,这时就会用到它。第一反应是,它会在这时提供一些有用的信息。但实际上它仅仅返回空,甚至没有去尝试获取任何东西。一样的,OpaqueImpl
处理不透明的类型并返回空。
在 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 字符串。 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
}
}
复制代码
除此以外,给相同值返回一个将 superclass
做为 subjectType
的新 Mirror
:
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
时能够意识到背后正在进行着什么。
本文由 SwiftGG 翻译组翻译,已经得到做者翻译受权,最新文章请访问 swift.gg。