By Mike Ash, translator: Nemocdz; Proofreading: Numbbbbb, Linus the little blacksmith; Finalized: Forelax
Despite its emphasis on static typing, Swift also supports rich metadata types. Metadata types allow code to examine and manipulate arbitrary values at run time. This functionality is exposed to Swift developers through the Mirror API. You might wonder how features like Mirror work in a language like Swift that places so much emphasis on static typing. Let’s take a look through this article.
Prior statement
Everything here is the details of the internal implementation. The version of the code is the version when the article was written, and the code may change with the version. Metadata becomes stable and reliable as the ABI stabilizes, but it is also prone to change when it arrives. Don’t rely on anything here when you’re writing your everyday Swift code. If you want to do more complex reflections than Mirror offers, here are some ideas. But until the ABI is stable, you need to keep an eye on the changes. If you want to use the Mirror itself, this article will provide some good ideas for how to access and adapt. But again, these things are subject to change with releases.
interface
The Mirror(reflecting:) initialization method can accept any value and return an instance that provides information about the collection of Children of that value. A Child consists of optional labels and values. You can use the Mirror to traverse the entire hierarchical view of the object on the value of the Child at compile time without knowing any type information. Mirror allows a type to provide a custom representation in a manner that follows the CustomReflectable protocol. This provides an effective way for types that want to be more friendly than the built-in form. For example, the Array type complies with the CustomReflectable protocol and exposes the element as untagged Children. Dictionary uses this method to expose key-value pairs as tagged Children. For other types, the Mirror uses magic to return a collection of Children based on the actual child elements within it. For structs and classes, Children are the property values stored therein. For tuples, Children are Children of the tuple. Enumerations are the case of the enumeration and its associated value, if any. How does all this magic work? Let’s take a look.
The code structure
The reflection API is partially implemented in Swift and partially implemented in C++. Swift is better suited for implementing more Swift interfaces and makes many tasks easier. The bottom layer of the Swift runtime is implemented in C++, but there is no direct access to C++ classes in Swift, so there is a C connection layer. Swift implementation of reflection is in reflectionmirror. Swift and C++ implementation is in reflectionmirror.mm. The two communicate via a small set of C++ functions exposed to Swift. Instead of using the SWIFt-generated C bridge layer, these functions are declared directly in Swift as specified custom symbols, and the C++ functions with these names are specifically implemented in a way that can be called directly by Swift. These two parts of code can interact without caring about how the bridging mechanism handles passing values behind the scenes, but you still need to know exactly how Swift should pass parameters and return values. Don’t try this unless you’re using run-time code that requires it. For example, let’s look at the _getChildCount function in reflectionMirror. swift:
@_silgen_name("swift_reflectionMirror_count")
internal func _getChildCount<T>(_: T, type: Any.Type) -> Int
Copy the code
The @_silgen_name modifier tells the Swift compiler to map this function to the swift_reflectionMirror_count symbol instead of the _getChildCount method name modifier that Swift normally corresponds to. Note that the leading underscore indicates that the modifier is reserved in the standard library. On the C++ side, the function looks like this:
SWIFT_CC(swift) SWIFT_RUNTIME_STDLIB_INTERFACE
intptr_t swift_reflectionMirror_count(OpaqueValue *value,
const Metadata *type,
const Metadata *T) {
Copy the code
SWIFT_CC(swift) tells the compiler that this function uses the Swift calling convention, not C/C++. SWIFT_RUNTIME_STDLIB_INTERFACE flag This is a function that is part of the interface on the Swift side, and it is also labeled extern “C” to avoid C++ method name modifiers and ensure that it has the expected symbol on the Swift side. At the same time, C++ arguments are deliberately matched to function calls declared in Swift. When Swift calls _getChildCount, C++ calls the function with the value of the pointer to the Swift value, the type of the type argument, and the function argument T of the corresponding type stereotype
. The entire interface of the Mirror between Swift and C++ consists of the following functions:
@_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
Copy the code
Magical dynamic distribution
There is no single, universal way to get any type of information we want. Tuples, structures, classes, and enumerations all require different code to do these various tasks, such as finding the number of child elements. There are also some deeper, subtle differences, such as the different handling of Swift and Objective-C classes. All of these functions need to be distributed in different implementation code because they require different types of checks. This sounds a bit like dynamic method dispatch, except that choosing which implementation to call is more complicated than checking the method used by the object type. The reflection code attempts to simplify the use of abstract base classes with interfaces containing C++ version information, and a large number of subclasses containing various cases for C++ dynamic distribution. A single function maps a Swift type to an instance of one of its C++ classes. Calls a method on an instance and dispatches the appropriate implementation. The mapping function is called call and is declared like this:
template<typename F>
auto call(OpaqueValue *passedValue, const Metadata *T, const Metadata *passedType,
const F &f) -> decltype(f(nullptr))
Copy the code
PassedValue is a pointer to the value of the Swift that is actually passed in. T is the static type of the value, corresponding to the paradigm parameter
in Swift. PassedType is the type that is explicitly passed to the Swift side and is actually applied during reflection (this type is different from the object type that is actually run when using Mirror as the parent instance). Finally, the f argument passes an object reference to the implementation that the function finds to be called. The function then returns the value that was returned when the f argument was called, making it easier for the user to get the return value. The call implementation wasn’t as exciting as it might have been. Basically a big switch declaration and some extra code to handle special cases. The important thing is that it ends the call to F with a subclass instance of ReflectionMirrorImpl, and then calls the methods on that instance to get the real work done. Here’s the ReflectionMirrorImpl, where everything from the interface is passed in:
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() {}
};
Copy the code
The interface function between Swift and the C++ component calls the corresponding method with call. For example, swift_reflectionMirror_count looks like this:
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();
});
}
Copy the code
Reflection of a tuple
Let’s take a look at tuple reflection, probably the simplest one, but it does a lot of work. It will initially return a display style of ‘t’ to indicate that this is a tuple:
struct TupleImpl : ReflectionMirrorImpl {
char displayStyle(a) {
return 't';
}
Copy the code
Although hard coded constants may not seem common, it is a reasonable choice to reference C++ and Swift in exactly the same place, and they do not need to use a bridge layer to interact. Next up is the count method. At this point we know that Type is actually a pointer of type TupleTypeMetadata and not just a pointer of type Metadata. TupleTypeMetadata has a NumElements field that records the number of elements in the tuple, and the method completes:
intptr_t count() {
auto *Tuple = static_cast<const TupleTypeMetadata *>(type);
return Tuple->NumElements;
}
Copy the code
The subscript method does a little more work. It also starts with the same static_cast function:
AnyReturn subscript(intptr_t i, const char **outName,
void (**outFreeFunc)(const char *)) {
auto *Tuple = static_cast<const TupleTypeMetadata *>(type);
Copy the code
Next, there is a boundary check to prevent the caller from requesting an index that does not exist in the tuple:
if (i < 0| | -size_t)i > Tuple->NumElements)
swift::crash("Swift mirror subscript bounds check failure");
Copy the code
The subscript serves two purposes: it retrieves the element and its name. For a structure or class, this name is the name of the stored property. In the case of tuples, the name is either the element’s tuple tag or, in the absence of a tag, a value indicator like.0. Labels are stored as a whitespace separated list in the Labels field of metadata. This code looks for the i-th string in the list:
// Check if 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; }}Copy the code
If there is no label, create an appropriate value indicator for the name:
if(! hasLabel) {// The name is the stringized element number '.0'.
char *str;
asprintf(&str, "%" PRIdPTR, i);
*outName = str;
}
Copy the code
Because Swift and C++ are used interchangeably, convenience features such as automatic memory management are not available. Swift has ARC and C++ has RALL, but the two technologies are not compatible. OutFreeFunc allows C++ code to provide a function to the caller to release the returned name. Tags need to be freed using free, so set the corresponding value to *outFreeFunc as follows:
*outFreeFunc = [](const char *str) { free(const_cast<char *>(str)); };
Copy the code
The name is notable, but the value is surprisingly easy to retrieve. The Tuple metadata contains a function that returns information about an element using an index:
auto &elt = Tuple->getElement(i);
Copy the code
The ELT contains an offset value that can be applied to a tuple value to get a pointer to the element’s value:
auto *bytes = reinterpret_cast<const char *>(value);
auto *eltData = reinterpret_cast<const OpaqueValue *>(bytes + elt.Offset);
Copy the code
The ELT also contains the type of the element. A new Any object containing the value can be constructed using a pointer to the type and value. This type has function Pointers that allocate memory and initialize stored fields containing values of the given type. Use these functions to copy objects of type Any and return Any to the caller. The code looks like this:
Any result;
result.Type = elt.Type;
auto *opaqueValueAddr = result.Type->allocateBoxForExistentialIn(&result.Buffer);
result.Type->vw_initializeWithCopy(opaqueValueAddr,
const_cast<OpaqueValue *>(eltData));
returnAnyReturn(result); }};Copy the code
That’s what tuples do.
swift_getFieldAt
Finding elements in structures, classes, and enumerations is pretty complicated right now. The main reason for this complexity is the lack of direct reference relationships between these types and the field descriptors of the fields that contain information about these types. There is a help function called swift_getField that finds the corresponding field descriptor for a given type. Once we add that direct reference, this whole function should be useless, but at the same time, it provides an interesting idea of how runtime code can use the language’s metadata to find type information. The function prototype looks like this:
void swift::_swift_getFieldAt(
const Metadata *base, unsigned index,
std::function<void(llvm::StringRef name, FieldType fieldInfo)>
callback) {
Copy the code
It checks by type, looks up by field index, and is called back when information is found. The first is to get the type context description of the type, which contains further information about the type to be used:
auto *baseDesc = base->getTypeContextDescriptor();
if(! baseDesc)return;
Copy the code
There are two parts to this work. The first step is to find the field descriptor for the type. The field descriptor contains all the field information about this type. Once the field descriptor is available, the function can look up the required information from the descriptor. Finding information from descriptors is encapsulated in a helper method called getFieldAt, which allows other code in a variety of places to find the appropriate field descriptor. Let’s look at the query process. It starts by getting a symbol restorer to restore the symbol-modified class name to the actual type reference:
auto dem = getDemanglerForRuntimeTypeResolution();
Copy the code
Caching is used to speed up multiple lookups:
auto &cache = FieldCache.get();
Copy the code
If the field descriptor is already in the cache, call getFieldAt to get:
if (auto Value = cache.FieldCache.find(base)) {
getFieldAt(*Value->getDescription());
return;
}
Copy the code
To make searching code simpler, there is a helper method that checks whether the FieldDescriptor is the one being searched. If the descriptor matches, the descriptor is put into the cache, getFieldAt is called, and success is returned to the caller. The matching process is complicated, but essentially boils down to matching the names decorated with symbols:
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;
};
Copy the code
Field descriptors can be registered at run time or put into binary at compile time. These two loops find all known field descriptors in the match:
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; }}Copy the code
When no match is found, a warning message is logged and an empty tuple is returned in the callback (just to give a callback) :
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));
}
Copy the code
It is worth noting the process of finding the field descriptor. GetFieldAt helps the method convert the field descriptor to the name and the field type returned in the callback. It starts by requesting a reference to a field from the field descriptor:
auto getFieldAt = [&](const FieldDescriptor &descriptor) {
auto &field = descriptor.getFields()[index];
Copy the code
The name can be accessed directly in this reference:
auto name = field.getFieldName(0);
Copy the code
If the field is actually an enumeration, there may be no type. Do this check first and execute the callback:
if(! field.hasMangledTypeName()) { callback(name, FieldType().withIndirect(field.isIndirectCase()));return;
}
Copy the code
A reference to a field stores the field type as a symbolic name. Because the callback expects a pointer to the metadata, the symbolic modified name must be converted to a real type. The _getTypeByMangledName function does most of the work, but requires the caller to resolve all of the generic arguments for this type. This work involves extracting the context from all the stereotypes of this type:
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(); }}Copy the code
Now that you have the name and type of the symbolic modifier, pass them into a Lambda expression to resolve the generic argument:
auto typeName = field.getMangledTypeName(0);
auto typeInfo = _getTypeByMangledName(
typeName,
[&](unsigned depth, unsigned index) -> const Metadata * {
Copy the code
If the request has a depth greater than the path size of the descriptor, it will fail:
if (depth >= descriptorPath.size())
return nullptr;
Copy the code
In addition, you get the generic parameters from the type of the field. This requires converting the index and depth into a single flat index, adding the number of generic parameters at each stage by traversing the path of the descriptor until the depth is reached:
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;
}
Copy the code
If the index is deeper than the generic parameter can reach, then it fails:
if (index >= currentContext->getNumGenericParams())
return nullptr;
Copy the code
In addition, get the appropriate generic parameters from the base type:
return base->getGenericArgs()[flatIndex];
});
Copy the code
As before, empty tuples are used if the type cannot be found:
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());
}
Copy the code
Then execute the callback, whatever is found:
callback(name, FieldType()
.withType(typeInfo)
.withIndirect(field.isIndirectCase())
.withWeak(typeInfo.isWeak()));
};
Copy the code
This is swift_getFieldAt. Let’s look at other reflection implementations with this helper method.
The reflection of the structure
The implementation of the structure is similar, but slightly more complex. This is because some structure types do not fully support reflection, it takes more effort to find names and offsets, and the structure may contain weak references that need to be extracted by reflection code. The first is a helper method to check that the structure fully supports reflection. The structure metadata stores such an accessible flag bit. As in the tuple code above, we know that type is actually a StructMetadata pointer, so we can pass it freely:
struct StructImpl : ReflectionMirrorImpl {
bool isReflectable(a) {
const auto *Struct = static_cast<const StructMetadata *>(type);
const auto &Description = Struct->getDescription();
return Description->getTypeContextDescriptorFlags().isReflectable();
}
Copy the code
The display style for the structure is S:
char displayStyle(a) {
return 's';
}
Copy the code
The number of child elements is the number of fields given by the metadata, or 0 (if the type does not actually support reflection) :
intptr_t count() {
if(! isReflectable()) {return 0;
}
auto *Struct = static_cast<const StructMetadata *>(type);
return Struct->getDescription()->NumFields;
}
Copy the code
As before, the subscript method is the tricky part. It starts similarly, doing boundary checking and finding offsets:
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];
Copy the code
Getting the type information from the structure field is a little more complicated. This is done with the _swift_getFieldAt help method:
Any result;
_swift_getFieldAt(type, i, [&](llvm::StringRef name, FieldType fieldInfo) {
Copy the code
Once it has field information, everything behaves like the code for the corresponding part of the tuple. Fill in the name and calculate the pointer stored in the field:
*outName = name.data();
*outFreeFunc = nullptr;
auto *bytes = reinterpret_cast<char*>(value);
auto *fieldData = reinterpret_cast<OpaqueValue *>(bytes + fieldOffset);
Copy the code
There is an extra step to copy the field value to the return value of type Any to handle weak references. LoadSpecialReferenceStorage method to deal with this situation. If the value is not loaded then the value is stored as normal and copied to the return value in the normal way:
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)); }});returnAnyReturn(result); }};Copy the code
So these are the structures that are interesting.
A reflection of class
Classes and structures are similar, and the code in ClassImpl is almost identical. There are two notable differences in working with Objective-C. One is the quickLookObject implementation, which invokes the Objective-C debugQuickLookObject method:
#if SWIFT_OBJC_INTEROP
id quickLookObject(a) {
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
Copy the code
If the parent of the class is an Objective-C class, the offset value of the field needs to be obtained at Objective-C runtime:
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
}
Copy the code
Enumeration of reflections
Enumerations have some differences. The Mirror considers an enumeration instance to contain at most one element, with the enumeration case name as the label and its associated value as the value. A case with no associated value does not contain elements. Here’s an example:
enum Foo {
case bar
case baz(Int)
case quux(String.String)}Copy the code
When a mirror is used for values of type Foo, the mirror shows that foo. bar has no child elements, foo. baz has an element of type Int, and foo. quux has an element of type (String, String). Classes and structs of the same sublabel and type have the same field values, but different enumeration cases of the same type do not. Associated values can also be indirect and require some special handling. The reflection of an enum requires four core pieces of information: the name of the case, the tag, the type of the payload, and whether it is an indirect payload. The getInfo method gets these values:
const char *getInfo(unsigned *tagPtr = nullptr.const Metadata **payloadTypePtr = nullptr.bool *indirectPtr = nullptr) {
Copy the code
Tags are retrieved directly from the request metadata:
unsigned tag = type->vw_getEnumTag(value);
Copy the code
Other information is retrieved using _swift_getFieldAt. Calling tag as a field index provides the appropriate information:
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();
});
Copy the code
All values are returned to the caller:
if (tagPtr)
*tagPtr = tag;
if (payloadTypePtr)
*payloadTypePtr = payloadType;
if (indirectPtr)
*indirectPtr = indirect;
return caseName;
}
Copy the code
You may be wondering why only the name of case is returned directly, while the other three are returned using Pointers. Why not return the type of tag or payload? The answer is: I really don’t know, it might seem like a good idea at that point.) The count method can use the getInfo method to retrieve the type of payload and return 0 or 1 to indicate whether the payload type is null:
intptr_t count() {
if(! isReflectable()) {return 0;
}
const Metadata *payloadType;
getInfo(nullptr, &payloadType, nullptr);
return(payloadType ! =nullptr)?1 : 0;
}
Copy the code
The subscript method starts by getting all the information about this value:
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);
Copy the code
The actual copy of the value requires more work. To handle indirect values, the whole process takes place in an extra box:
const Metadata *boxType = (indirect ? &METADATA_SYM(Bo).base : payloadType);
BoxPair pair = swift_allocBox(boxType);
Copy the code
In the indirect case, the true value is retrieved from box:
if (indirect) {
const HeapObject *owner = *reinterpret_cast<HeapObject * const *>(value);
value = swift_projectBox(const_cast<HeapObject *>(owner));
}
Copy the code
Now everything is ready. Add a sublabel to the case name:
*outName = caseName;
*outFreeFunc = nullptr;
Copy the code
A familiar approach is used to return payload as an object of type 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);
}
Copy the code
The remaining species
There are three other implementations in the file, each of which does almost nothing. ObjCClassImpl handles objective-C classes. It doesn’t even try to return any child elements, because Objective-C allows too many remedies for ivars’ content. Objective-c classes allow wild Pointers to remain in place and require separate logic for implementations not to touch that value. Because such a value attempts to return as a Mirror child, it violates Swift’s security guarantee. Because there is no way to reliably tell what to do if a value goes wrong, the code avoids dealing with the entire situation. MetatypeImpl handles meta-types. This is used when you use Mirror on an actual type, such as Mirror(reflecting: string.self). The first reaction is that it will provide some useful information at this point. But in fact it just returns nothing and doesn’t even try to get anything. Similarly, OpaqueImpl handles opaque types and returns null.
Swift side interface
On the Swift side, the Mirror calls the interface functions implemented on the C++ side to retrieve the required information and present it in a more user-friendly manner. This is done in the Mirror initializer:
internal init(internalReflecting subject: Any,
subjectType: Any.Type? = nil,
customAncestor: Mirror? = nil)
{
Copy the code
SubjectType is the type of the value of the subject to be reflected. This is usually the runtime type of the value, but it can be the parent class if the caller uses the superclassMirror to find the hierarchy of classes above. If the caller does not pass in subjectType, the code will ask the C++ side for the type of subject:
let subjectType = subjectType ?? _getNormalizedType(subject, type: type(of: subject))
Copy the code
It then gets the number of child elements and creates a collection that later gets the individuals of each child element to build the children object:
let childCount = _getChildCount(subject, type: subjectType)
let children = (0 ..< childCount).lazy.map({
getChild(of: subject, type: subjectType, index: $0)})self.children = Children(children)
Copy the code
The getChild function is a simple wrapper around C++’s _getChild function that converts the C string contained in the label name to the Swift string. Mirror has a superclassMirror property, which returns a Mirror object that has checked the properties of the class at the next level in the class hierarchy. Internally, it has a _makeSuperclassMirror property that holds a closure of the Mirror that builds its parent class on demand. The closure starts by fetching the subjectType parent. Non-class types and classes without a parent have no parent Mirror, so they get nil:
self._makeSuperclassMirror = {
guard let subjectClass = subjectType as? AnyClass.let superclass = _getSuperclass(subjectClass) else {
return nil
}
Copy the code
The caller can specify the behavior of the custom ancestor with a Mirror instance that can be returned directly as the parent Mirror:
if let customAncestor = customAncestor {
if superclass == customAncestor.subjectType {
return customAncestor
}
if customAncestor._defaultDescendantRepresentation == .suppressed {
return customAncestor
}
}
Copy the code
In addition, return a new Mirror with superclass as subjectType for the same value:
return Mirror(internalReflecting: subject,
subjectType: superclass,
customAncestor: customAncestor)
}
Copy the code
Finally, it retrieves and parses the displayed style and sets the remaining properties of the 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
}
Copy the code
conclusion
Swift’s rich metadata types exist mostly behind the scenes, enabling things like protocol conformance checking and generic type resolution. Some of these are exposed to the user through the Mirror class, allowing arbitrary values to be checked at run time. This approach may seem strange and mysterious at first for the statically typed Swift ecosystem, but based on the information that already exists, it’s actually a straightforward application. This implementation journey should help you understand the mysteries and be aware of what’s going on behind the scenes when you use the Mirror.
This article is translated by SwiftGG translation team and has been authorized to be translated by the authors. Please visit swift.gg for the latest articles.