Swift 中存在反射嗎?

2021-03-02 iOS成長指北

感謝你閱讀本文,如果你喜歡的話,歡迎關注「 iOS 成長指北 」,如果本文對你有所幫助,歡迎點讚、收藏、分享

儘管 Swift 如此的強調靜態類型、編譯時安全以及靜態調度,但它仍然在其標準庫中提供了一種反射機制。當然,人們經常聲稱,反射並沒有真正在 Swift 中得到運用。

「反射(Reflection)」 是一種常見的程式語言特性,它使我們能夠在運行時動態地檢查和處理類型的成員。這似乎有悖於 Swift 對編譯時驗證的高度關注,雖然 Swift 對反射的實現肯定比您其他語言中的要有限,但它從第一天起就一直存在。開發者可以通過 「Mirror」 API 來允許代碼在運行時檢查和操縱任意值。

Swift 版本的反射功能使我們能夠遍歷類型具有的所有存儲屬性(無論是結構,類還是任何其他類型)並讀取其值,從而實現一種元編程(meta programming),使我們能夠編寫實際上與代碼本身交互的代碼。

Swift 中的反射是「只讀」的,不能修改任何屬性。但是,它仍然相當強大。

參照維基百科對計算機科學中反射的定義,我們可以用一個新的術語 「內省(Introspection)」 ——一種查看對象屬性而不修改它們的方法則被稱為「內省」 來形容 Swift 中的反射。

不過本文還是以發射作為術語,哪怕其實他只是內省。

學習目標

通過完成本文的學習,我們主要解決以下幾個問題

簡述一下 「Mirror」 的實現原理—— How Mirror Work?Mirror

在開發者官網上,Mirror 其存在於 Debugging and Reflection,用於查詢運行時的值。

A representation of the substructure and display style of an instance of any type.

——表示任何類型實例的子結構和顯示樣式

PS:「當我們在開發者官網上查詢我們不了解的工具時,筆者建議起碼完成對 Overview 部分的閱讀」

struct Point {
    let x: Int, y: Int
}

let p = Point(x: 21, y: 30)
print(String(reflecting: p))
// Prints "▿ Point
//           - x: 21
//           - y: 30"

創建一個 Mirror 實例

Mirror 是一個結構體並且提供了一個構造器 public init(reflecting: Any) ,現在我們用這個構造器生成一個 Mirror 實例並列印實例對象

let names = ["Zaphod", "Slartibartfast", "Trillian", "Ford", "Arthur", "Marvin"]
let nameMirror = Mirror(reflecting: names)
print(nameMirror)
//Mirror for Array<String>

nameMirror 就是 names 數組——或者更準確的說是Array<String> 的鏡像。

如同其默認構造器public init(reflecting subject: Any) 所定義的一樣,其 subject 是一個 「Any」。作為 Swift 中最為通用的類型,可能在 Swift 中所有的實例都遵循該類型,這使得這使得 Mirror 得以兼容 Struct、Class、Enum、Tuple、Array、Dictionary、Set 等幾乎各種類型。

實際上,Any 是一個空協議,所有的類型都隱式地遵循這個協議。

❞什麼是 Mirror

我們來看一下 Mirror 的文檔,對整個 Mirror Struct 有一個了解

public struct Mirror {

    public enum AncestorRepresentation {
        /// Generates a default mirror for all ancestor classes.
        /// This case is the default when initializing a `Mirror` instance.
        case generated
        /// Uses the nearest ancestor's implementation of `customMirror` to create
        /// a mirror for that ancestor.
        case customized(() -> Mirror)
      /// Suppresses the representation of all ancestor classes.
        ///
        /// In a mirror created with this ancestor representation, the
        /// `superclassMirror` property is `nil`.
        case suppressed
    }

    public init(reflecting subject: Any)

    public typealias Child = (label: String?, value: Any)

    public typealias Children = AnyCollection<Mirror.Child>

    public enum DisplayStyle {
        case `struct`
        case `class`
        case `enum`
        case tuple
        case optional
        case collection
        case dictionary
        case set
        
        ...
    }
    ...

    public let subjectType: Any.Type

    public let children: Mirror.Children
  
    public let displayStyle: Mirror.DisplayStyle?

    /// A mirror of the subject's superclass, if one exists.
    public var superclassMirror: Mirror? { get }
}

「DisplayStyle」 枚舉,列舉了 Mirror 所支持展現的類型。之前我們說過,Mirror 幾乎兼容 Swift 中的所有類型,那麼有什麼是例外的呢?如果你詳細閱讀過筆者的 《Swift in 100 Days》,你會發現之其中並沒有定義函數/閉包(Closure)——閉包是一種類型特殊的函數。

Mirror 定義了一個 「typealias」 Child 來修飾其存在結構的子元素。

 public typealias Child = (label: String?, value: Any)

每個子元素都有一個可選的標籤和類型為 Any 的值。不是所有反射所支持的結構都具有具體名字的子結構。Struct/Class 以屬性的名稱作為 label,但是在集合中只有索引,沒有名稱。不過元組有點特別,我們可以元組定義標籤——不過這不是必要的,所以在 Swift 中元組的鏡像標籤為「Optional(".0")」 、「Optional(".1")」 ...

「AncestorRepresentation」 該枚舉用於定義如何反射被反射的 「subject」 的超類。也就是說,這只用於 Class 類型的 「subject」。默認情況,Swift 會為每個超類生成一個額外的鏡像。但是,如果需要更大的靈活性,可以使用 AncestorRepresentation 枚舉來定義如何反射超類。

在 Swift 中使用 Mirror

現在我們對 Mirror 中重要的屬性、方法和枚舉都有一個簡單的了解。下面我將通過幾個例子來說明這些東西具有如何使用。

「displayStyle: Mirror.DisplayStyle?」 :「subject」 的顯示樣式「let subjectType: Any.Type」 :「subject」 的類型「let children: Children」 :「subject」 的子元素「func superclassMirror() -> Mirror?」 : 「subject」 超類的鏡像DisplayStyle

在上面我們提到關於 「DisplayStyle」 枚舉所不包含的類型——函數/閉包,如果我們為函數/閉包創建一個 Mirror 並列印其 「displayStyle」 會發生什麼呢?

let names = ["Zaphod", "Slartibartfast", "Trillian", "Ford", "Arthur", "Marvin"]
let nameMirror = Mirror(reflecting: names)
print(nameMirror) //Mirror for Array<String>
print(nameMirror.displayStyle as Any) //Optional(Swift.Mirror.DisplayStyle.collection)

let closure = { (a: Int) -> Int in return a * 2 }
let closureMirror = Mirror(reflecting: closure)
print(closureMirror) //Mirror for (Int) -> Int
print(closureMirror.displayStyle as Any) //nil

func greetUser(name: String) {
    print("Hello,\(name)!")
}
let squaredMirror = Mirror(reflecting: greetUser)
print(squaredMirror) //Mirror for (String) -> ()
print(squaredMirror.displayStyle as Any) //nil

雖然你可以得到一個鏡像,但是 函數/閉包 的顯示樣式為 nil。

SubjectType

被反射的 「subject」 的靜態類型。

print(closureMirror.subjectType)
// (Int) -> Int
print(squaredMirror.subjectType)
//(String) -> ()
print(Mirror(reflecting: (1, 2, "3")).subjectType)
// (Int, Int, String)
print(Mirror(reflecting: 5).subjectType)
// Int
print(Mirror(reflecting: "test").subjectType)
// String
print(Mirror(reflecting: NSNull()).subjectType)
// NSNull

值得注意的一點是在蘋果文檔上附帶了這麼一句:「當這個 Mirror 是另一個 Mirror 的超類的 Mirror 時,這個類型可能與 subject 的動態類型不同。」

這裡指出的兩點是 「可能」 和 「動態類型」 。筆者對此不太理解,希望有大佬予以指教。

Children

子元素的集合,描述了所反射 「subject」 的結構。這可能是最常用的一個功能了,用來獲取當前實例屬性集合,用於表示其結構。

我們基於下面的例子來幫我們了解其結構

class Vehicle {
    var wheels:Int = 0
    var maxSpeed:Int = 0

    func drive() {
        print("This vehicle is driving!")
    }
    deinit {
        print("This vehicle is all!")
    }
}

class RaceCar: Vehicle {
    var hasSpoiler = true
    var accessories = Accessories()
    var competitionTypes = ["Road race","Cross country", "Rally"]
    
    override func drive() {
        print("VROOOOM!!!")
    }
}

struct Accessories {
    var material = "leather"
    var color = "red"
    var type: Use = .char
    var price = 100
    
    enum Use {
        case char
        case speaker
        case steeringWheel
    }
}

當我們創建一個基於RaceCar 的實例時,我們通過 children 這個屬性來獲取實例的結構

let ferrari = RaceCar()

let ferrariMirror = Mirror(reflecting: ferrari)
for case let (label?, value) in ferrariMirror.children {
    print (label, value)
}
//hasSpoiler true
//accessories Accessories(material: "leather", color: "red", type: __lldb_expr_3.Accessories.Use.char, price: 100)
//competitionTypes ["Road race", "Cross country", "Rally"]

for property in ferrariMirror.children {
    print("name: \(String(describing: property.label)) type: \(type(of: property.value))")
}
//name: Optional("hasSpoiler") type: Bool
//name: Optional("accessories") type: Accessories
//name: Optional("competitionTypes") type: Array<String>

到這裡可以很容易的發現,我們可以獲取屬性名、屬性類型以及屬性值。

引申

在我們開頭的文檔部分,我們知道 children 屬性的實現是基於一個別名

typealias Mirror.Children = AnyCollection<Mirror.Child>
...
public let children: Mirror.Children

這是一個 AnyCollection 類型的值。AnyCollection 是一個對所有具有支持「向前遍歷的索引」的集合進行 「type-erased」 的包裝 。

有時候我們可以針對這個 AnyCollection 做一些處理,譬如列印部分屬性?

if let b = AnyBidirectionalCollection(ferrariMirror.children) {
    for element in b.suffix(5) {
        print(element)
    }
}
//(label: Optional("hasSpoiler"), value: true)
//(label: Optional("accessories"), value: __lldb_expr_15.Accessories(material: "leather", color: "red", type: __lldb_expr_15.Accessories.Use.char, price: 100))
//(label: Optional("competitionTypes"), value: ["Road race", "Cross country", "Rally"])

這裡我們列印前當前結構的 5 個屬性——不過我們的實例只有三個屬性都會被列印出來。

當然還有一個 「type-erased」 的概念可能會讓你難以理解。這是一個有點花哨的概念,「將關聯類型轉換為通用約束」 。Swift 中的 「Any...」 系列的標準庫基本都實現了這個功能。如果你有所期待,可以與筆者交流。筆者在自己的 《Swift in 100 Days》 的 作用域與泛型 中提及了關聯類型的概念,這可能對你有所幫助。

superclassMirror

在上例中,我們只能看到當前 subject 的屬性值,我們的 RaceCar 其繼承於 Vehicle 對象。

public var superclassMirror: Mirror? { get }

當該對象不是 class-based 的,其結果是一個可選值,否則其為一個新的 Mirror 實例。

print(ferrariMirror.superclassMirror as Any)

for property in ferrariMirror.superclassMirror!.children {
    print("name: \(String(describing: property.label)) type: \(type(of: property.value))")
}
//Optional(Mirror for Vehicle)
//name: Optional("wheels") type: Int
//name: Optional("maxSpeed") type: Int

我們通過這個可以判斷實例是否存在超類以及其超類的屬性。

Use Case

任何脫離實際使用的功能或概念,談論起來都是 紙上談兵。僅僅是列印實例的屬性——聽起來倒是很刺激,但是你不能修改它!!!那麼除了讓我們檢查實例的值我們還有什麼其他用例呢?下面我們一一闡述。

自動執行相同的操作

假設我們正在進行開發的 APP 有一個 UserSession 的類用來跟蹤用戶登錄以後的部分操作。例如,用戶的基本信息、用戶的點讚/收藏/喜歡信息以及用戶的設置等存儲類——我們為了能夠在調用時能夠便捷的時候而生成這些存儲類。作為一個單會話維持的應用,在用戶登出時,我們需要重置這些存儲類。

// 創建一個通用的 重置方法
protocol Resettable {
    func reset()
}

extension UserInfoStorage: Resettable { ... }
extension FavoritesStorage: Resettable { ... }
extension SettingsStorage: Resettable {...}

class UserSession {
    var userInfo: UserInfoStorage
    var favorites: FavoritesStorage
    var settings: SettingsStorage
    ...
}

當我們的用戶登出時,我們重置我們的信息即:

extension UserSession {
    func logOut() {
        userInfo.reset()
        favorites.reset()
        settings.reset()
    }
}

我們的會話需要存儲多少個存儲類信息,我們就需要在 logOut() 方法中執行多少個 reset() 方法。那麼有沒有什麼比較優雅的方法呢?

有同學會提到,通過遍歷 UserSession 的全部屬性,當屬性符合 Resettable 協議時,讓該屬性調用  reset() 方法。不錯,那麼如何遍歷 UserSession 的全部屬性呢?

在 OC 中我們可以獲取類的屬性列表。但在 Swift 中如何解決呢?沒錯。就是使用 「Mirror」 的 「Children」

func logOut() {
  let mirror = Mirror(reflecting: self)
  for child in mirror.children {
    if let resettable = child.value as? Resettable {
      resettable.reset()
    }
  }
}

現在,如果我們向 UserSession 添加新的 Resettable 屬性,則在調用 logOut() 將自動調用  reset() 。

當我們遇到需要執行遍歷 「不定數量」 但具有相同「方法」的屬性,不失為一個好方法。

上述方法只能針對特定的 reset() 方法且需要存在 Resettable 屬性,我們還可以更加細緻的拓展一下,使其支持執行不需要參數方法的遵循特定類的方法。這裡需要用到泛型相關知識,不熟悉的讀者可以參照筆者之前關於泛型的文章 作用域與泛型 。

extension Mirror {
    static func reflectProperties<T>(
        of target: Any,
        matchingType type: T.Type = T.self,
        using closure: (T) -> Void
    ) {
        let mirror = Mirror(reflecting: target)

        for child in mirror.children {
            (child.value as? T).map(closure)
        }
    }
}

現在我們就可以對我們的 logOut() 方法進行修改,並且當我們需要相似場景式,也支持這麼調用

extension UserSession {
    func logOut() {
        Mirror.reflectProperties(of: self) {
            (child: Resettable) in
            child.reset()
        }
    }
}

使用 CustomReflectable 進行調試

可以使用 CustomReflectable 來幫我們調試。如果我們對默認提供的類型不滿意時,可以使其符合 CustomReflectable 並返回自定義鏡像實例。

作為 lldb 極為常見的一個命令,po:

(lldb) po self

<Landmark.ViewController: 0x7f96a643e0d0>

拓展一下我們的 UIViewController

extension UIViewController: CustomReflectable {
    public var customMirror: Mirror {
        let children = KeyValuePairs<String, Any>(dictionaryLiteral:("title", title!))
        return Mirror(NSUserActivity.self, children: children, displayStyle: .class, ancestorRepresentation: .suppressed)
    }
}

然後我們就在再點擊 po 命令

(lldb) po self
▿ <Landmark.ViewController: 0x7f96a643e0d0>
  - title : "Turtle Rock"

有一點需要注意的是, title 屬性需要進行賦值。

更多

筆者在尋找 Swift 中反射的資料時,發現了一個利用反射實現的自動 JSON 編碼的 Github 倉庫 Wrap—— 將對象轉換成為 JSON 也許對你有些幫助。

How Mirror Work?

作為一門開源語言,我們可以通過 Swift 的源碼來了解其中的實現原理。本篇文章基於 Swift 5.3.3 release 版本的代碼。後續如有更新,筆者也會跟進。目前 ABI 已經穩定,理論上應該不會出現太大的波動。筆者參照了 Swift.org 這篇文章 How Mirror Works ,如果英文閱讀有難度的話,我們可以查看 SwiftGG 的這篇譯文 Mirror 的工作原理。

代碼結構

反射的 API 有一部分是用 Swift 實現的,另一部分是用 C++ 實現的。Swift 更適合用在實現更 Swift 的接口,並讓很多任務變得更簡單。Swift 的運行時的底層是使用 C++ 實現的。反射的 Swift 實現在 ReflectionMirror.swift ,其 C++ 實現文件為 ReflectionMirror.cpp。

需要注意的是,在新版的代碼中存在一個 ReflectionMirrorObjC.mm 是對 Objective-C 的類的不同處理,讀者不要找錯了。

這兩者通過一小組暴露給 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 的函數參數來調用此函數。

動態派發

所有的這些函數因為需要不同類型的檢查而需要派發不同的實現代碼。這聽起來有點像動態方法派發,除了選擇哪種實現去調用比檢查對象類型所使用的方法更複雜之外。這些反射代碼嘗試去簡化使用包含 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 參數調用時的返回值,可以讓使用者更方便的獲得返回值。

重要的是它會用一個 ReflectionMirrorImpl 的子類實例去結束調用 f,然後會調用這個實例上的方法去讓真正的工作完成。

這都是基於反射實現的抽象基類 ReflectionMirrorImpl

// Abstract base class for reflection implementations.
struct ReflectionMirrorImpl {
  const Metadata *type;
  OpaqueValue *value;
  
  virtual char displayStyle() = 0;
  virtual intptr_t count() = 0;
  virtual intptr_t childOffset(intptr_t index) = 0;
  virtual const FieldType childMetadata(intptr_t index,
                                        const char **outName,
                                        void (**outFreeFunc)(const char *)) = 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
  
  // For class types, traverse through superclasses when providing field
  // information. The base implementations call through to their local-only
  // counterparts.
  virtual intptr_t recursiveCount() {
    return count();
  }
  virtual intptr_t recursiveChildOffset(intptr_t index) {
    return childOffset(index);
  }
  virtual const FieldType recursiveChildMetadata(intptr_t index,
                                                 const char **outName,
                                                 void (**outFreeFunc)(const char *))
  {
    return childMetadata(index, outName, outFreeFunc);
  }

  virtual ~ReflectionMirrorImpl() {}
};

反射的實現

當我們需要用到反射式,作用在 Swift 和 C++ 組件之間的接口函數就會用 call 去調用相應的方法。

我們專注於 Children 這個屬性值的獲取,來看一下 Swift 中發射的實現。

在 Mirror 文檔中,Children 是這麼定義的

public typealias Children = AnyCollection<Mirror.Child> 

在  ReflectionMirror.swift 文件中,我們發現其實其實基於這部分實現的

self._children = _Children(
      ReflectedChildren(subject: subject, subjectType: subjectType))

「ReflectedChildren」 是一個實現 subscript() 方法的,其獲取 Child 的方法就是這個

subscript(index: Int) -> Child {
  getChild(of: subject, type: subjectType, index: index)
}

其獲取出來的數據是

internal func getChild<T>(of value: T, type: Any.Type, index: Int) -> (label: String?, value: Any) {
  var nameC: UnsafePointer<CChar>? = nil
  var freeFunc: NameFreeFunc? = nil
  
  let value = _getChild(of: value, type: type, index: index, outName: &nameC, outFreeFunc: &freeFunc)
  
  let name = nameC.flatMap({ String(validatingUTF8: $0) })
  freeFunc?(nameC)
  return (name, value)
}

我們看到,其值的獲取的 _getChild 函數在編譯時會@_silgen_name 修飾符而映射成 C++ 的方法 swift_reflectionMirror_subscript:

// We intentionally use a non-POD return type with this entry point to give
// it an indirect return ABI for compatibility with Swift.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wreturn-type-c-linkage"
// func _getChild<T>(
//   of: T,
//   type: Any.Type,
//   index: Int,
//   outName: UnsafeMutablePointer<UnsafePointer<CChar>?>,
//   outFreeFunc: UnsafeMutablePointer<NameFreeFunc?>
// ) -> Any
SWIFT_CC(swift) SWIFT_RUNTIME_STDLIB_API
AnyReturn swift_reflectionMirror_subscript(OpaqueValue *value, const Metadata *type,
                                           intptr_t index,
                                           const char **outName,
                                           void (**outFreeFunc)(const char *),
                                           const Metadata *T) {
  return call(value, T, type, [&](ReflectionMirrorImpl *impl) {
    return impl->subscript(index, outName, outFreeFunc);
  });
}
#pragma clang diagnostic pop

函數 call 會用一個 ReflectionMirrorImpl 的子類實例去結束調用 f,然後會調用這個實例上的方法去完成真正的工作。

根據數據的類型不同,有以下幾種不同的 ReflectionMirrorImpl 的子類實例

「TupleImpl」:數據類型為元組(Tuple)類型「StructImpl」:數據類型為結構(Struct)類型「EnumImpl」:數據類型為枚舉(Enum)和可選(Optional)「ClassImpl」 和 「ObjCClassImpl」:數據類型為 Swift 或 Objective-C 中的 「類」。由於 ObjC 無法保證 ivars 的狀態,因此這個方法可能是「不安全的」「MetatypeImpl」:數據類型為 Metatype 或 ExistentialMetatype「OpaqueImpl」:處理不透明的類型並返回空。getFieldAt

在結構、類和枚舉中查找元素目前來說相當複雜。造成這麼複雜的主要原因是,這些類型和包含這些類型相關信息的欄位的欄位描述符之間缺少直接的引用關係。

我們通過 getFieldAt 函數幫助我們查找出給定類型相應的欄位描述符。

下面的代碼進行簡單的截取,並添加注釋:

static std::pair<StringRef /*name*/, FieldType /*fieldInfo*/>
getFieldAt(const Metadata *base, unsigned index) {
  using namespace reflection;
  //如果我們找不到該類型的欄位描述符元數據,則返回一個空的元組作為替代。
  auto failedToFindMetadata = [&]() -> std::pair<StringRef, FieldType> {
    ...
    return {"unknown", FieldType(&METADATA_SYM(EMPTY_TUPLE_MANGLING))};
  };
  // 獲取類型的類型上下文描述
  // 判斷獲取的上下文是否符合需要,如果不符合則返回空的元組
  auto *baseDesc = base->getTypeContextDescriptor();
  if (!baseDesc)
    return failedToFindMetadata();

  auto *fields = baseDesc->Fields.get();
  if (!fields)
    return failedToFindMetadata();
  
  auto &field = fields->getFields()[index];
  // Bounds are always valid as the offset is constant.
  auto name = field.getFieldName();

  // 如果這個欄位實際上是一個枚舉,那麼就可能沒有類型。
  if (!field.hasMangledTypeName())
    return {name, FieldType::untypedEnumCase(field.isIndirectCase())};

  auto typeName = field.getMangledTypeName();
  
  SubstGenericParametersFromMetadata substitutions(base);
  // 欄位的引用將欄位類型儲存為一個符號修飾的名字。因為回調預期的是元數據的指針,所以符號修飾的名字必須被轉化為一個真實的類型。
  auto result = swift_getTypeByMangledName(
      MetadataState::Complete, typeName, substitutions.getGenericArgs(),
      [&substitutions](unsigned depth, unsigned index) {
        return substitutions.getMetadata(depth, index);
      },
      [&substitutions](const Metadata *type, unsigned index) {
        return substitutions.getWitnessTable(type, index);
      });

  // 拆解類型失敗,假裝它是一個空類型而不是日誌信息
  TypeInfo typeInfo;
  if (result.isError()) {
    ...
  } else {
    typeInfo = result.getType();
  }
  // 用獲取到的真實類型的值
  auto fieldType = FieldType(typeInfo.getMetadata());
  fieldType.setIndirect(field.isIndirectCase());
  fieldType.setReferenceOwnership(typeInfo.getReferenceOwnership());
  fieldType.setIsVar(field.isVar());
  return {name, fieldType};
}

結構體的反射

因為有些結構體類型不完全支持反射,查找名字和偏移值要花費更多力氣,而且結構體可能包含需要反射代碼去提取的「弱引用」

下面的代碼進行簡單的截取,並添加注釋:

// Implementation for structs.
struct StructImpl : ReflectionMirrorImpl {
  //檢查結構體是否支持反射。結構體元數據裡儲存了一個標記是否可以反射的欄位
  bool isReflectable() {
    const auto *Struct = static_cast<const StructMetadata *>(type);
    const auto &Description = Struct->getDescription();
    return Description->isReflectable();
  }
  // 結構體的 displayStyle 是 's'
  char displayStyle() override {
    return 's';
  }
  //子元素的數量是元數據給出的欄位的數量,可能是 0 —— 如果這個類型實際上不能支持反射的話
  intptr_t count() override {
    if (!isReflectable()) {
      return 0;
    }

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

  intptr_t childOffset(intptr_t i) override {
    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.
    return Struct->getFieldOffsets()[i];
  }
  //通過 getFieldAt 函數獲取結構體的屬性信息
  const FieldType childMetadata(intptr_t i, const char **outName,
                                void (**outFreeFunc)(const char *)) override {
    StringRef name;
    FieldType fieldInfo;
    std::tie(name, fieldInfo) = getFieldAt(type, i);
    assert(!fieldInfo.isIndirect() && "indirect struct fields not implemented");
    
    *outName = name.data();
    *outFreeFunc = nullptr;
    
    return fieldInfo;
  }

  AnyReturn subscript(intptr_t i, const char **outName,
                      void (**outFreeFunc)(const char *)) override {
    auto fieldInfo = childMetadata(i, outName, outFreeFunc);
    //根據偏移量和數據來獲取元素的值
    auto *bytes = reinterpret_cast<char*>(value);
    auto fieldOffset = childOffset(i);
    auto *fieldData = reinterpret_cast<OpaqueValue *>(bytes + fieldOffset);
    // 拷貝欄位的值到 Any 類型的返回值來處理弱引用
    return copyFieldContents(fieldData, fieldInfo);
  }
};

小結

至此,我們基本梳理了一下 Swift 中結構體反射的實現。反射是通過 「動態派發」  ReflectionMirrorImpl 的子類實例來實現。

引申閱讀

如果讀者關於 Swift 的動態特性有所期待的話,可以關注一下 raywenderlich 的 Dynamic Features in Swift。後續我們可能會對其中的 「Dynamic Member Lookup」 做一次討論。

文章每周持續更新,歡迎關注「 iOS 成長指北 」第一時間閱讀催更。一起學習,獲得成長。如果本文對你有所幫助,歡迎點讚、分享、收藏

感謝你的閱讀!

相關焦點

  • iOS-Swift 結構體與類
    __allocating_init()libswiftCore.dylib: swift_allocObjectlibswiftCore.dylib: swift_slowAlloclibsystem_malloc.dylib: malloc 在Mac、iOS中的 malloc 函數分配的內存大小總是16 的倍數。
  • Xcode 9.3 新增能力,優化 Swift 編譯生成代碼的尺寸
    -Osize 根據項目不同,大致可以優化掉 5% - 30% 的代碼空間佔用。 相比 -0 來說,會損失大概 5% 的運行時性能。在之前的 XCode 版本,這兩個選項和 -O 是連在一起設置的,Xcode 9.3 中,將他們分離出來,可以獨立設置:
  • 神經反射的原理
    反射中樞是在中樞神經系統中調節某一特定生理功能的神經元群,是反射活動最複雜、最關鍵的環節。反射越簡單,反射中樞所在部位越局限,反射越複雜,則中樞分布越廣泛。例如,瞳孔對光反射的中樞局限在中腦,而呼吸反射中樞則位於脊髓、延髓、腦幹及大腦皮層。
  • 傳輸線與反射
    沒有人知道到底是什麼產生了反射電壓?只是知道這樣產生之後,交界面兩側的電壓才能相等,交界面處的電壓才是連續的。同理,在交界面兩側也存在電流迴路,電流也是連續的。這樣,整個系統才是平衡的。傳輸線的端接匹配有3種重要的特殊情況。假設傳輸線的特性阻抗是50Ω,信號由源端沿傳輸線到達有特殊終端阻抗的遠端。注意:在時域中信號對受到的瞬時阻抗十分敏感。
  • 【Golang】圖解反射
    我們已經介紹過runtime包中_type、uncommontype、eface、iface等類型了,reflect也要和它們打交道,但是它們都屬於未導出類型,所以reflect在自己的包中又定義了一套,兩邊的類型定義是保持一致的。
  • 光遇鏡子反射,「雷射武器」也是一種光,能用鏡子防禦嗎?
    雷射天地導讀:光遇鏡子反射,「雷射武器」也是一種光,能用鏡子防禦嗎?光武器的原理極為簡單,利用了原子躍遷時產生的高能量雷射束,產生破壞性的高能雷射束來消滅目標。這其中的原因涉及到光學的基本原理。每個鏡子都不可能做到百分百的反射光線,這和鏡子的材料有關係,只有完全理想狀態下的鏡子才能完全反射光線。然而我們不需要理想狀態,只需要鏡子的純度達到一定程度,能夠將光部分反射,同時保證折射的光線不會燒穿鏡子即可。不過這樣的鏡子比較難製造,鏡面得採用特種玻璃才可以,用普通的肯定是不達標的。
  • Swift 4.0 正式發布,更快更兼容更好用
    編譯器支持兩種語言模式:語言模式由-swift-version指定給編譯器,由Swift包管理器和Xcode自動處理。在macOS上,Swift軟體包的構建現在會在一個防止網絡訪問和文件系統修改的沙箱中進行,有助於減輕惡意程序的影響。
  • Cadence Allegro 17.2 新的反射流程讓信號反射仿真分析更加便捷高效
    反射(reflection)和我們所熟悉的光經過不連續的介質時都會有部分能量反射回來一樣,都是信號在傳輸線上的回波現象。此時信號功率沒有全部傳輸到負載處,有一部分被反射回來現象。 對這個就是一個信號反射模型喔,在高速的PCB中導線必須等效為傳輸線,按照傳輸線理論,如果源端與負載端具有相同的阻抗,反射就不會發生了。
  • 紅警中的磁爆步兵現實中真的存在嗎?
    在遊戲裡面,磁暴步兵採用了磁能電磁槍對敵人進行攻擊,憑藉這種技術,使得磁爆步兵在近戰時有著非常強悍的攻擊力和破壞力,而且這種步兵在面對集群裝甲的時候也可以輕鬆擊穿,那麼,一定會有人問在現實中真的存在這種武器嗎?
  • 時域反射電纜測長中的波速特性
    為了減小波速的影響,研究了時域反射電纜測長中的波速特性,分析了行波傳播速度和介電常數的頻率特性,研究了電纜的材料特性和環境溫度對電纜中行波傳播速度的影響,總結了確定波速的依據. 實驗測量了行波信號在不同溫度下的波速,給出了行波在PVC 絕緣電纜中傳播速度的溫度補償係數. 實驗結果表明,經過波速溫度補償後,時域反射電纜測長系統的測量精度得到顯著提高.
  • 串擾和反射影響信號的完整性
    差的信號完整性不是由某一單一因素導致的,而是板級設計中多種因素共同 引起的。當電路中信號能以要求的時序、持續時間和電壓幅度到達接收端時,該電路就有很好的信號完整性。當信號不能正常響應時,就出現了信號完整性問題。
  • 在iOS 8中使用UIAlertController
    版本和Objective-C版本不同,在swift中,alertView的初始化只允許創建擁有一個取消按鈕的對話框視圖。在這個示例中,我們將前面的示例中的「好的」按鈕替換為了「重置」按鈕。以前我們只能在默認視圖、文本框視圖、密碼框視圖、登錄和密碼輸入框視圖中選擇,現在我們可以向對話框中添加任意數目的UITextField對象,並且可以使用所有的UITextField特性。當您向對話框控制器中添加文本框時,您需要指定一個用來配置文本框的代碼塊。
  • Unity| Specular高光反射初探(上)
    大家好今天聊兩分鐘的Specular高光(鏡面)反射我是木偶心沒哈嘍,上次我們有分享過Diffuse漫反射的基本光照原理Unity | Diffuse Light漫反射光照,現在我們來聊一下另外一個反射,specular鏡面或者高光反射。
  • go那些事兒|go反射第一彈(TypeOf)
    Go語言程序的反射系統無法獲取到一個可執行文件空間中或者是一個包中的所有類型信息,需要配合使用標準庫中對應的詞法,語法解析器和抽象語法樹(AST)對源碼進行掃描後獲得這些信息。 Go語言提供了 reflect 包來訪問程序的反射信息。
  • 「戰狼中隊」是特種部隊中的特種部隊,在現實中真的存在嗎?
    特種兵的訓練與生活在所有兵種中最辛苦、最殘酷。無論軍人的身體狀態如何,特種兵每天的訓練都不會斷掉,需要自行去克服身體上的各種問題。而且特種兵的工作範圍不僅僅是保護國家,特種兵還會走上國際戰場開展維和行動。在電影《戰狼》中,主角冷鋒說了一段話:「戰狼中隊是特種部隊中的特種部隊,因為戰狼中隊中的成員全都是其他特種部隊精心挑選出來的優秀特種兵。」
  • 條件反射的新類型?
    神經系統的調節功能是通過不同形式的反射來完成的。由遺傳因素決定的、先天具有的反射稱為非條件反射。在非條件反射的基礎上,經過後天學習和訓練後建立起來的反射稱為條件反射。條件反射大大增強了機體活動的預見性、靈活性和精確性,使機體對環境具有更加廣闊和完善的適應能力。
  • 《神存在嗎?》7.必然的存在(史鮑爾)
    還有一個必須首先提及的定律是慣性定律,運動中的物體傾向於保持運動狀態,除非有外力的作用。而靜止的物體傾向於保持靜止狀態,直到受到外力作用。例如,想像一個人打高爾夫球,球在球座上時是靜止的,除非有外力作用,例如被球手的球桿擊打,否則球會持續靜止。被擊打後,球會持續運動,直到阻力使其減速,例如風的阻力。最終,球會停下來,因著地面的摩擦力停止滾動。
  • 世界真實存在嗎,不過是一個程序?
    我們極有可能是未來人類模擬出來的虛擬存在,甚至天體物理學家尼爾泰森和企業家伊隆馬斯克都認為世界是一個被設計的程序。 因此我們可以合理懷疑,在500年甚至1000年後,我們的後代會製造一個超級計算機,完整的把整個地球模擬出來,每一處細節都和現實中沒什麼兩樣,每一個生物的意識也都可以模擬,那麼我們如何否認這還有沒有發生又存不存在呢?
  • 光的反射定律及平面鏡成像—鍾朝光
    :反射光線與入射光線、法線在同一平面上,反射光線和入射光線分居於法線的兩側,反射角等於入射角.光的反射過程中光路是可逆的。由圖還可以看出顯示光點之間的距離與液面升降的高度之間存在著正比關係:,即接收平面上的光點S1和S2之間的距離只與液面升降的高度有關,與接收平面到液面的距離無關。
  • 淺談 Java 反射技術
    一、什麼是反射?反射 (Reflection) 是 Java 的特徵之一,它允許運行中的 Java 程序獲取自身的信息,並且可以操作類或對象的內部屬性。簡而言之,通過反射,我們可以在運行時獲得程序或程序集中每一個類型的成員和成員的信息。程序中一般的對象的類型都是在編譯期就確定下來的,而 Java 反射機制可以動態地創建對象並調用其屬性,這樣的對象的類型在編譯期是未知的。所以我們可以通過反射機制直接創建對象,即使這個對象的類型在編譯期是未知的。