12. 反射-Mirror

187 阅读2分钟

前言

我们都知道OC是一门动态语言,它的动态特性就是基于Runtime,正因为Runtime的存在,OC 才可以在运行时创建类、进行消息转发和方法交换等。那么Swift作为一门静态语言,它有Runtime吗?这里我们可以研究一下。

一、Runtime

这里我们按照OCruntime用法来获取Swift类的属性和方法,直接添加代码:

import Foundation

class Person {
    var age = 3

    func run() {
        print("run")
    }
}

do {
    var methodCount: UInt32 = 0
    let methodlist = class_copyMethodList(Person.self, &methodCount)
    for i in 0..<numericCast(methodCount) {
        if let method = methodlist?[i] {
            let methodName = method_getName(method)
            print("methodName:\(String(describing: methodName))")
        } else {
            print("methodName not found")
        }
    }

    var propertyCount: UInt32 = 0
    let propertyList = class_copyPropertyList(Person.self, &propertyCount)
    for i in 0..<numericCast(propertyCount) {
        if let property = propertyList?[i] {
            let propertyName = property_getName(property)
            print("propertyName:\(String(utf8String: propertyName)!)")
        } else {
            print("propertyName not found")
        }
    }
}

执行之后可以发现Person类的方法和属性都为空。如果我们在方法和属性前面都加上@objc标识,会发生什么?

import Foundation

class Person {
    @objc var age = 3

    @objc func run() {
        print("run")
    }
}

do {
    var methodCount: UInt32 = 0
    let methodlist = class_copyMethodList(Person.self, &methodCount)
    for i in 0..<numericCast(methodCount) {
        if let method = methodlist?[i] {
            let methodName = method_getName(method)
            print("methodName:\(String(describing: methodName))")
        } else {
            print("methodName not found")
        }
    }

    var propertyCount: UInt32 = 0
    let propertyList = class_copyPropertyList(Person.self, &propertyCount)
    for i in 0..<numericCast(propertyCount) {
        if let property = propertyList?[i] {
            let propertyName = property_getName(property)
            print("propertyName:\(String(utf8String: propertyName)!)")
        } else {
            print("propertyName not found")
        }
    }
}

/* 执行结果
 methodName:init
 methodName:age
 methodName:run
 methodName:setAge:
 propertyName:age
 Program ended with exit code: 0
 */

执行之后可以看到Person类的方法和属性都能正常打印了。这个如果我们去桥接文件看一下,还会发现一个问题

image-20220114112746403

从桥接文件中无法找到Person类的相关信息,也就是说OC是无法调用Swift中的Person类。此时我们再让Person类继承自NSObject,执行之后发现桥接文件中会出现Person类的相关信息:

image-20220114114556763

如果此时我们把方法和属性前的@objc都去掉再执行,则会发现执行结果是这样的:

image-20220114115020904

再查看桥接文件:

image-20220114115116080

这里可以看到Person类只有一个初始化方法,别的啥都没有。结合之前分析过的方法调度,这里我们可以总结一下:

  1. 对于纯Swift类来说,方法和属性不加任何修饰符的情况下,这个时候其实已经不具备所谓的Runtime特性了。
  2. 对于纯Swift类,方法和属性添加@objc标识的情况下,当前我们可以通过Runtime API拿到, 但是在我们的OC中是没法进行调度的。
  3. 对于继承自NSObject类来说,如果我们想要动态的获取类的属性和方法,必须在其声明前添加@objc关键字,否则也是没有办法通过Runtime API获取的。
  4. Swift类没有动态性,但在方法、属性前添加dynamic修饰,可获得动态性。
  5. 继承自NSObjectSwift类,其继承自父类的方法具有动态性,其它自定义方法、属性想要获得动态性,需要添加dynamic修饰。
  6. 若方法的参数、属性类型为Swift特有(无法映射到OC的类型,如CharacterTuple),则此方法、属性无法添加dynamic修饰(编译器报错)。

那么纯Swift类没有Runtime的特性,我们该如何获取类的方法、属性呢?

二、Mirro(反射)

所谓反射就是可以动态获取类型、成员信息,在运⾏时可以调⽤⽅法、属性等⾏为的特性。在使⽤OC开发时很少强调其反射概念,因为OCRuntime要⽐其他语⾔中的反射强⼤的多。而Swift是⼀⻔类型安全的语⾔,它不⽀持我们像OC那样直接操作,但它的标准库提供了反射机制来让我们访问成员信息。Swift的反射机制是基于⼀个叫 Mirror 的结构体来实现的。为具体的实例创建⼀个Mirror对象,然后就可以通过它查询这个实例。

1. 基本用法

这里我们直接添加代码:

import Foundation

class Person {
    var age = 3

    func run() {
        print("run")
    }
}

let p = Person()
p.run()

let mirror = Mirror(reflecting: p)
mirror.children.forEach { item in
    print("\(String(describing: item.label)):\(item.value))")
}

/* 执行结果
 run
 Optional("age"):3)
 Program ended with exit code: 0
 */

执行之后可以发现,遍历 mirror.children 就可以将Person的方法和属性打印出来。我们查看一下 mirror.children

image-20220117133344825

这里可以看到 mirror.children 只是存放元祖类型 Child 的集合。那么它的值是如何来的,我们去源码中查看一下。

2. 源码分析

这里我们在 Mirro.swift 中找到初始化方法:

image-20220115103120185

这里可以看到,首先通过模式匹配判断 subject 是否遵循了 CustomReflectable,是的话就调customMirror,否则就走 internalReflecting 初始化。那么 CustomReflectable 协议是什么?我们可以看一下:

image-20220115103847033

这是可以看到,我们正常调试时 po p 打印的仅仅是地址,如果我们增加了 CustomReflectable 协议:

image-20220115104807967

这是可以看到,我们调试时 po p 打印的除了地址还有其它属性。有点类似自定义的 description 效果,我们调试时可以这么用。接下来我们继续查看 internalReflecting 初始化方法:

image-20220117133915235

这里我们通过全局搜索可以找到该初始化方法,大致看一下,这里做了几件事:

  1. 调用 _getNormalizedType 获取 subject 的具体类型
  2. 调用 _getChildCount 获取该类型的 childCount
  3. childCount 大于0时调用 getChild 获取 child
  4. 将这些 child 储存到 children

这里查看一下 _getNormalizedType_getChildCountgetChild

image-20220117134842915

从这里可以看到这几个方法前都用编译器字段 @_silgen_name 标识,这是 Swift 的一个隐藏符号,作用是将某个 C/C++ 语言函数直接映射为 Swift 函数。 这里我们可以试一下,先定义一个C函数

image-20220115135553015

image-20220115140144757

image-20220115140221338

这里可以看到,我们定义的 c_max(C函数) 被映射成 swift_max(Swift函数),并能够正常调用。回到源码中,_getNormalizedType_getChildCountgetChild 最终调的是:

  1. swift_reflectionMirror_normalizedType
  2. swift_reflectionMirror_count
  3. swift_reflectionMirror_subscript

这里我们分别搜索一下:

先搜索 swift_reflectionMirror_normalizedType 可以找到:

image-20220115141125578

这里可以看到 swift_reflectionMirror_normalizedType 调了call后返回 impl->type

再搜索 swift_reflectionMirror_count

image-20220117141526840

这里可以看到 swift_reflectionMirror_count 调了call后返回的 impl->count

最后搜索 swift_reflectionMirror_subscript

image-20220117141753931

这里可以看到 swift_reflectionMirror_subscript 调了call后返回的 impl->subscript。到这里我们可以发现这些方法有个共同点,它们都在调 call。这里我们接着搜索 call

image-20220115170001482

image-20220115170118722

image-20220115161715453

ReflectionMirror.mm 中可以看到 call 方法的具体实现,注意这里定义了 callcallClass 两个闭包,通过后面的模式匹配代码可以发现除了类调 callClass最后也会调call) ,其它的如枚举、结构体都是调 call 。查看 call 可以得知传入的信息都是 ReflectionMirrorImpl 类型,那么这个 ReflectionMirrorImpl 是什么? 我们直接搜索 ReflectionMirrorImpl

20220115171720394

这里可以看到这个 ReflectionMirrorImpl 其实是抽象类。再查看模式匹配的逻辑可以得知不同类型在调 call 时传入的 impl 不同,比如 纯Swift类是ClassImplOC类是ObjCClassImpl), 当然这些 impl 都实现了抽象类 ReflectionMirrorImpl,也就是说这些 impl 都有 type(MetaData)countsubscript。 这里我们看一下 ClassImpl(截取片段):

image-20220117143210966

这里可以看到 ClassImpl 的具体信息, 它有个属性isReflectable,首先是将 type 强转成 ClassMetadata ,然后或取它的 Description,最后取 DescriptionisReflectable,这个 isReflectable 就是标识当前类型是否支持反射。另外我们可以看到 countsubscript。 先看 count,它是在获取 ClassMetadataDescription, 然后从 Description 中取 NumFields。这里我们搜索一下 ClassMetadata

image-20220115173817652

image-20220115173913568

image-20220115173944693

image-20220115174035573

这里可以看到 ClassMetadata其实是 TargetClassMetadat 的别名,从 TargetClassMetadat 中找到 Description 属性,这个 DescriptionTargetClassDescriptor 类型,在这个类中可以找到 NumFields,它表示属性数量。回到 ClassImpl 的具体信息中国,我们接着看 subscript

image-20220117144058770

这里可以看到,该方法其实是在通过偏移量 fieldOffsetClassMetadata 中取属性值。其实不管是类还是其它类型(如枚举,结构体等)都有自己的 MetaData,这个 MetaData 中包含了该类型的相关信息,而 Mirror 的底层是在操作 MetaData,从而获取我们需要的信息(比如 FieldDescrition)。

三、总结

这里我们总结一下 Mirror 的步骤:

  1. 初始化时调了 call(OpaqueValue *passedValue, const Metadata *T, const Metadata *passedType, const F &f) -> decltype(f(nullptr))
  2. call中通过 getKind 获取对应实例的类型,并创建对应类型的 ReflectionMirrorImpl
  3. ReflectionMirrorImpl 中包含了 type(MetaData)countsubscript
  4. 通过 ReflectionMirrorImplcount 获取属性个数。
  5. 通过 ReflectionMirrorImplsubscript 获取属性信息。