听说 Swift 存在反射编程?

听说 Swift 存在反射编程?

点赞评论,感觉有用的朋友可以关注笔者公众号 iOS 成长指北,持续更新

尽管 Swift 如此的强调静态类型、编译时安全以及静态调度,但它仍然在其标准库中提供了一种反射机制。当然,人们经常声称,反射并没有真正在 Swift 中得到运用。

反射(Reflection) 是一种常见的编程语言特性,它使我们能够在运行时动态地检查和处理类型的成员。这似乎有悖于 Swift 对编译时验证的高度关注,虽然 Swift 对反射的实现肯定比您其他语言中的要有限,但它从第一天起就一直存在。开发者可以通过 Mirror API 来允许代码在运行时检查和操纵任意值。

Swift 版本的反射功能使我们能够遍历类型具有的所有存储属性(无论是结构,类还是任何其他类型)并读取其值,从而实现一种元编程(meta programming),使我们能够编写实际上与代码本身交互的代码。

Swift 中的反射是 只读 的,不能修改任何属性。但是,它仍然相当强大。

参照维基百科对 计算机科学中反射 的定义,我们可以用一个新的术语 内省(Introspection) ——一种查看对象属性而不修改它们的方法则被称为内省 来形容 Swift 中的反射。

不过本文还是以发射作为术语,哪怕他只是内省。

学习目标

通过完成本文的学习,我们主要解决以下几个问题

  1. Mirror 到底是什么?有哪些使用方式?
  2. 在实际项目中,这种有限制的反射我能能做些什么?
  3. 简述一下 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.Typesubject 的类型
  • let children: Childrensubject 的子元素
  • 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 中如何解决呢?没错。就是使用 MirrorChildren

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)
  • ClassImplObjCClassImpl: 数据类型为 Swift 或 Objective-C 中的 。由于 ObjC 无法保证 ivars 的状态,因此这个方法可能是不安全的
  • MetatypeImpl: 数据类型为 MetatypeExistentialMetatype
  • 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
标签: