Swift的Mirror反射

785 阅读10分钟

1、元类型

1.1、AnyObject

  • 任意类的 instance类的类型仅类遵守的协议(结构体不能遵守)
    class LZPerson: Run {
        var age: Int = 20
        var weight: Double = 181.5
    
        func running() -> Self {
            print("person is running")
            return self
        }
    }
    
    protocol Run: AnyObject {
        func running() -> Self
    }
    
    let p  = LZPerson()
    let p1 : AnyObject = p
    let p2 : AnyObject = LZPerson.self
    let age: AnyObject = (p as! LZPerson).age as NSNumber
    
    • 在和 OC 交互的过程中,也经常通过 AnyObject 来表示某种类型的 instance

    • 在代码编写过程中不知道具体类型的时候用 AnyObject 表示,如果确定类型之后可以通过 asas?(不确定类型时尝试转换,如果转换失败则返回nil)as! 三种关键字进行类型转换。

    • 不能用 Int 等Swift底层是结构体的类型进行赋值

1.2、AnyClass

  • Xcode代码中可以看到其实就是 AnyObject.Type 的类型别名 image.png

1.3、Any

  • 代表任意类型,包括 funcation 类型或者 Optional 类型,对应的是 OC的id 类型

1.4、type(of:T)

  • Self 基本可以理解为同一个东西,⽤来获取⼀个值的动态类型;需要理解下动静态类型,静态类型指的是在编译时期确定了的类型,而动态类型则是在运行时期才确定的类型

  • 当协议P作为字符串的静态类型,传入一个泛型参数,并且未使用 As Any 转换时,会返回 P.self 而不是 String.self(系统解释的) image.png

    image.png

    • stringAsP字符串遵守P协议,被传入形参value,因为是 Any 类型所以编译期间编译器并不能知道这个形参的类型,在泛型函数的主体中,类型参数不是静态已知的协议类型。 因此,type(of:) 只能产生具体的元类型 P.Protocol,要获得准确的动态类型,需要转换为 Any 类型 image.png

1.5、self

  • T.self:
    • T是实例对象,当前 T.self返回的就是他本身
    • T是类,当前 T.self 返回的就是元类型
  • T.self 属于 T.type

1.6、Self

  • 用于直接获取当前类的元类型,然后可以用它直接访问类的类属性与类方法

  • 在协议声明或协议成员声明中,Self 类型是指最终符合协议的类型

  • 多用在 方法的返回值协议

    func running() -> Self {
        print("person is running")
        return self
    }
    

2、Swift中的Runtime

  • 对于纯 Swift 类来说,方法和属性不加任何修饰符的情况下。这个时候其实已经不具备我们所谓的Runtime 特性了。

  • 对于纯 Swift 类,方法和属性添加@objc标识的情况下,当前我们可以通过 Runtime API 拿到,但是在我们的 OC 中是没法进行调度的

  • 对于继承自 NSObject 类来说,如果我们想要动态的获取当前的属性和方法,必须在其声明前添加@objc 关键字,否则也是没有办法通过Runtime APl 进行获取。

  • 纯Swift类没有动态性,但在方法、属性前添加dynamic修饰,可获得动态性。

  • 继承自NSObject的Swift类,其继承自父类的方法具有动态性,其它自定义方法、属性想要获得动态性,需要添加dynamic修饰。

  • 若方法的参数、属性类型为Swift特有、无法映射到Objective一C的类型(如Character、Tuple),则此方法、属性无法添加 dynamic 修饰(编译器报错)

3、Mirror

  • 所谓反射就是可以动态获取类型、成员信息,在运⾏时可以调⽤⽅法、属性等⾏为的特性。在使⽤OC开发时很少强调其反射概念,因为OC的Runtime要⽐其他语⾔中的反射强⼤的多。但是 Swift 是⼀⻔类型安全的语⾔,不支持我们像OC那样直接操作,它的标准库仍然提供了反射机制来让我们访问成员信息函数是不能直接访问到的,除非定义成属性信息

  • Swift 的反射机制是基于⼀个叫 Mirror 的 结构体 来实现的 image.png

    • Mirror(reflecting: Any).children :获取反射出的属性集合,.label属性名.value属性值

3.1、案例应用

image.png

  • 因为类中属性 也可能为包含其他属性的类或结构体等 ,所以对 child.value 值再调用 mirrorTest 方法进行递归操作,直到操作的属性 不再含有子属性,通过图中 guard 判断返回这个属性自身,供上一层的 result[key] 存为字典

  • gurad 关键字: 

    • 条件检测:⽐如我们在上⾯中的代码如果当前⼦属性为空,那么就直接返回⾃身,避免了 if

3.2、改进案例

  • 为了能将转字典的方法抽取,提供给更多的对象使用,我们把具体方法放到 协议 里,并增加 抛出异常 的功能 image.png
    • 如果我们想要展示更多的错误信息,系统给我们提供了 LocalizedError 协议

3.3、Mirror源码

说明CustomReflectable
  • 首先找到初始化方法,看到一个CustomReflectable协议,如果遵守则走协议流程,那它有什么作用呢? image.png
    • 这⾥接受⼀个 Any 类型的参数,同样的这⾥有⼀个 if case 的写法来判断当前的 subject 是否遵循了 customReflectable 协议,如果是我们就直接调⽤ customMirror , 否则就进⾏下级函数的调⽤;遵循协议并实现后,po出来的会有具体值,而不仅是地址 image.png

    • if case : 其实是枚举 Case 的模式匹配,和我们的 Switch ⼀样,这⾥是只有⼀个 case 的 switch 语句

3.3.1、C++方法映射Swift方法
  • Mirror 底层通过使用编译器隐藏符号@_silgen_name将C++方法映射到Swift方法 的方式,大量使用了C++方法,以下为部分映射关系,具体可到源码查询,命名方式都 以下划线"_"开始 image.png
    • 正常来讲调用一个C函数,我们需要在 .h中声明方法.c中实现桥接文件中导入.h文件 image.png
    • 但使用 @_silgen_name后,桥接文件中不需导入.h, .h中也无需声明方法,直接在.c中找实现方法 image.png
3.3.2、流程分析
  • 根据上边init方法知道,不走 CustomReflectable 协议方法的话会进入Mirror(internalReflecting:subject,subjectType,customAncestor)方法 image.png
    1. 获取属性 : 看到类型 subjectType 是由_getNormalizedType()方法得到,其中参数需要 subject及其真实类型,因为subject类型是 Any,所以需要使用type(of:)方法;对应的C++方法为:(传进来的subject及其类型在映射方法中变成了OpaqueValue与Metadata类型) image.png

      • 再继续找到 call 函数的实现,这⾥其实是⼀个回调函数,当前回调的具体数据都是由 ReflectionMirrorImpl(抽象类) 结构体实现,传递过来的 subject及其type存到impl中 image.png
        • 根据抽象类的具体类不同(subject的真实类型调用getKind;如 StructImplEnumImpl 等),switch 进class, struct, enum, Tuple 等的具体实现 image.png
      • ReflectionMirrorImpl 抽象类结构体 : 包含 属性数量偏移量 等信息(具体实现还要看具体的Impl类型,这里是提供笼统的内容) image.png
        • childMetadatasubscript方法也看一下: image.png
          • getFieldAt(): image.png
      • EnumImpl为例,看看除了基结构体的属性外还有哪些其他的: image.png
        • 先看到的是一个 isReflectable 的方法,这个方法表示 是否可以被反射,通过强转拿到EnumMetadata,再找到 Enum 中存储的Description,通过它里面存储的 isReflectable来确定

        • 数量由对抽象类 ReflectionMirrorImpl 的方法intptr_t count()重写得到 image.png

        • 再找到获取信息的getInfo的方法 image.png

        • getFieldAt:获取Descriptor image.png 可以看到上一层 getFieldAt(type, tag) 传进来的 type 在这层中是Metadata类型的,叫做 base,这下我们就知道了Mirror辗转一圈最后归结于获取 Metadata 数据结构;因为流程比较繁琐,下面拿到第4大点进行单独分析

    2. 获取属性数量 : 看到属性数量 childCount 由方法_getChildCount()得到;对应的C++方法为: image.png

      • 可以与上边获取 type 的方法进行对比,发现差别只是 ReflectionMirrorImplreturnimpl->type 变成了 ->count()
    3. 获取存放属性信息的数组 : image.png

  • 总结一下,流程分析其实就是分析怎么通过 ReflectionMirrorImpl 把属性的相关内容存到找到的,而获取信息的方法是通过访问 Metadata 实现

4、Metadata源码结构分析

4.1、StructMetadata

  • 我们以 StructImpl 为例进行类似 EnumImpl 的分析,可以知道最终传到 getFieldAt() 方法中的 Metadata 是 StructMetadata,所以我们在源码中首先对其进行搜索(并逐步找寻父类),找到了这么一句: image.png 原来底层将 StructMetadata 起了个别名叫TargetStructMetadata,而且还给好多方法起了别名,随便看看就行,图中也仅是一小部分 image.png

  • TargetStructMetadata 结构体内容就在取别名的方法上边 image.png

    • 这里 Description 需要关注,涉及类型TargetStructDescriptor
  • TargetValueMetadata image.png

    • 这里也有 Description 参数,涉及类型TargetValueTypeDescriptor正是上边 TargetStructDescriptor的父类 image.png image.png
      • 这里有两个重要属性:NumFieldsFieldOffsetVectorOffset
        1. NumFields : 主要表示结构体中属性的个数,如果只有一个字段偏移量则表示偏移量的长度
        2. FieldOffsetVectorOffset : 表示这个结构体元数据中存储的属性的字段偏移向量的偏移量,如果是0则表示没有
  • TargetMetadata: image.png

    • Kind属性是一个 UInt64(64位机器,32位机器则为UInt32) 指针 image.png
  • 大概可以归纳 TagerStructMetadata 结构为

    struct TagerStructMetadata {     
        var kind:UnsafePointer<UInt64>     
        var Description: UnsafeMutablePointer 
    }
    

4.2、TargetValueTypeDescriptor

  • 继续寻找父类内容 image.png

    • getKind:获取 Metadata 类型 image.png
  • TargetTypeContextDescriptor image.png

    • Name : 就是类型的名称
    • AccessFunctionPtr : 是该类型元数据访问函数的指针
    • Fields : 是一个指向该类型的字段描述符的指针
      • 研究这个就需要跳到 FieldDescriptor 中看一下,又能看到5个属性 image.png
  • TargetContextDescriptor: descriptors 的最终基类 image.png

    • Flags : 是描述上下文的标志,包括它的种类和格式版本
    • Parent : 是记录父类上下文的,如果是顶级则为null,查看其类型TargetRelativeContextPointer<Runtime>发现是RelativeIndirectablePointer的别名 image.png
      • RelativeIndirectablePointer image.png
        • RelativeOffsetPlusIndirect: 存储在内存中的对象属性存储相对的地址偏移量 image.png
          • 直接地址就是地址直接存0x1004,但偏移地址只需要存更少的内容只有一个4,再通过一个 基地址 ,就能访问到数据,具体可以在一个applyRelativeOffset中查到 image.png

4.3、属性名和属性值

  • 3.3.2一开始的图中能看到获取属性的方法为getChild,中间过程省略一下,与上边说的找方法方式一样,能找到流程为: image.png
    1. ReflectionMirrorImpl的impl 调用 subscript -> childMetadata获取到fieldInfo获取 属性名
    2. 通过childOffset函数和index获取到偏移量fieldOffset,最后根据内存偏移去到 属性值

总结

  • 总的来说,整个流程都是通过 Metadata , getDescription() , FieldDescrition 这⼏个东⻄来去实现的,⼀个是 当前类型的元数据,⼀个是 当前类型的描述,⼀个是 对当前类型属性的描述

结构图引用了: juejin.cn/post/705339… 的,相信对大家的理解会有帮助 image.png

归纳StructMetadata结构

struct StructMetaData{
    var kind : Int32
    
    //从TargetValueMetadata继承
    var typeDescriptor : UnsafeMutablePointer<StructDescriptor>
}

struct StructDescriptor {
    //Flags、Parent从TargetContextDescriptor继承
    //描述上下文的标志,包括其Kind和Version
    let flags: Int32   
    //父上下文,如果为顶级上下文,则为null
    let parent: Int32
    
    //结构体的名称
    var name: RelativePointer<CChar>   
    var AccessFunctionPtr: RelativePointer<UnsafeRawPointer>    
    //记录属性内容
    var Fields: RelativePointer<FieldDescriptor>
    
    //结构中存储的属性数
    var NumFields: Int32    
    //属性在元数据中字段偏移向量的偏移量
    var FieldOffsetVectorOffset: Int32
}

//记录结构体内所有属性的结构
struct FieldDescriptor {
    var MangledTypeName: RelativePointer<CChar>
    var Superclass: RelativePointer<CChar>
    var kind: UInt16
    var fieldRecordSize: Int16
    var numFields: Int32
    //连续存储空间 (有几个数据,就会在后面添加几个记录,通过内存平移读取)
    var fields: FieldRecord
}

struct FieldRecordT<Element> {
    var element: Element
    mutating func element(at i: Int) -> UnsafeMutablePointer<Element> {

        return withUnsafePointer(to: &self) {
            return UnsafeMutablePointer(mutating:  UnsafeRawPointer($0).assumingMemoryBound(to: Element.self).advanced(by: i))
        }
    }
}

//每个属性的内容
struct FieldRecord {
    var Flags: Int32
    var MangledTypeName: RelativePointer<CChar>
    var FieldName: RelativePointer<CChar>
}

// 相对位移指针,相当于RelativeIndirectPointer和RelativeDirectPointer
struct RelativePointer<T> {
    var offset: Int32
    // 偏移offset位置,获取内容指针
    mutating func get() -> UnsafeMutablePointer<T>{
        let offset = self.offset
        //withUnsafePointer获取指针
        // UnsafeMutablePointer 返回T类型对象的指针
        // UnsafeRawPointer将p指针转换为未知类型
        // advanced进行内存偏移
        // numericCast将offset转换为偏移单位数
        // assumingMemoryBound绑定指针为T类型
        return withUnsafePointer(to: &self) { p in
            return UnsafeMutablePointer(mutating: UnsafeRawPointer(p).advanced(by: numericCast(offset)).assumingMemoryBound(to: T.self))
        }
    }
}

解析自定义Struct

struct LZPerson {
    var age: Int = 20
    var name: String = "LZ"
}

//UnsafeMutablePointer<StructMetaData>.self  获取当前 struct 的 Metadata
//利⽤强转函数 unsafeBitCast 按位转换内存指针
//把 LZPerson Metadata 转成还原出来的 StructMetaData
let ptr = unsafeBitCast(LZPerson.self as Any.Type, to: UnsafeMutablePointer<StructMetaData>.self)

//通过属性内存的访问,获取到 name 字段的内存地址
let namePtr = ptr.pointee.typeDescriptor.pointee.name.get()

//经过 String 函数输出,得到我们结构体的名字
print("current class name: \(String(cString: namePtr))")

//获取属性的个数
let numFields = ptr.pointee.typeDescriptor.pointee.NumFields
print("当前类属性的个数: \(numFields)")

//获取属性的描述信息
let fieldDespritor = ptr.pointee.typeDescriptor.pointee.Fields.get()

//遍历属性
for i in 0..<numFields {
    //获取属性的信息
    let record = withUnsafePointer(to: &fieldDespritor.pointee.fields){
        return UnsafeMutablePointer(mutating: UnsafeRawPointer($0).assumingMemoryBound(to: FieldRecord.self).advanced(by: numericCast(i)))
    }
    
    //读取属性的名称
    let recordNameStr = String(cString: record.pointee.FieldName.get())
    //读取属性的类型
    let manNameStr = String(cString: record.pointee.MangledTypeName.get())
    print("类型名称:\(recordNameStr)---- 类型:\(manNameStr)")
}