Mirror 的工做原理

做者: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 協議而且暴露其中的元素爲無標籤的 ChildrenDictionary 使用這種方法暴露其中的鍵值對爲帶標籤的 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_getFieldAt

在結構、類和枚舉中查找元素目前來講至關複雜。形成這麼複雜的主要緣由是,這些類型和包含這些類型相關信息的字段的字段描述符之間缺乏直接的引用關係。有一個叫 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 &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 表達式來解決範型參數:

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 側接口

在 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

相關文章
相關標籤/搜索