感謝你閱讀本文,如果你喜歡的話,歡迎關注「 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 成長指北 」第一時間閱讀催更。一起學習,獲得成長。如果本文對你有所幫助,歡迎點讚、分享、收藏
感謝你的閱讀!