Swift进阶-Mirror使用及源码解析

1,900 阅读12分钟
  • 我们知道Swift是一门静态语言,但让类继承NSObject,然后在属性和方法前面添加@objc,可以让它动态的处理一些事情。其实Swift本身也可以动态的获取类的成员及类型,这种方式就叫做反射(Mirror)

Mirror的Api简介

  • Mirror是一个结构体,它的Api如下:

    struct Mirror {
        public enum DisplayStyle : Sendable {
            case `struct`
            case `class`
            case `enum`
            case tuple
            case optional
            case collection
            case dictionary
            case set
            public static func == (a: Mirror.DisplayStyle, b: Mirror.DisplayStyle) -> Bool
            public func hash(into hasher: inout Hasher)
            public var hashValue: Int { get }
        }
        // 初始化
        public init(reflecting subject: Any)
        public enum AncestorRepresentation {
            case generated
            case customized(() -> Mirror)
            case suppressed
        }
        public init<Subject, C>(_ subject: Subject, children: C, displayStyle: Mirror.DisplayStyle? = nil, ancestorRepresentation: Mirror.AncestorRepresentation = .generated) where C : Collection, C.Element == Mirror.Child
        public init<Subject, C>(_ subject: Subject, unlabeledChildren: C, displayStyle: Mirror.DisplayStyle? = nil, ancestorRepresentation: Mirror.AncestorRepresentation = .generated) where C : Collection
        public init<Subject>(_ subject: Subject, children: KeyValuePairs<String, Any>, displayStyle: Mirror.DisplayStyle? = nil, ancestorRepresentation: Mirror.AncestorRepresentation = .generated)
        public let displayStyle: Mirror.DisplayStyle?
    
        // 反射主体的类型
        public let subjectType: Any.Type
        public typealias Child = (label: String?, value: Any)
        public typealias Children = AnyCollection<Mirror.Child>
        public let children: Mirror.Children
      
        // 父mirror,没有则为nil
        public var superclassMirror: Mirror? { get }
        public func descendant(_ first: MirrorPath, _ rest: MirrorPath...) -> Any?
    }
    
    • Api主要分为三类:
        1. 4个初始化方法:
        • init(reflecting subject: Any)是最简单的初始化方法,subject是实例对象
        1. 成员变量:
        • displayStyle: 反射主题在枚举DisplayStyle中对应的类型
        • children: 是一个集合,结合的元素是元组,元祖由可选变迁label,和值value构成。
        • subjectType: 反射主体的类型
        1. 方法:descendant

Mirror简单使用

  • 下面来通过案例介绍mirror的一些简单用法

    struct WSTeacher {
        var name: String = "wushuang"
        var age: Int = 18
    }
    let t = WSTeacher()
    let mirror = Mirror(reflecting: t)
    
    for obj in mirror.children {
        print("\(obj.label!) : \(obj.value)")
    }
    
    • 此案例可以将struct的成员变量以及其在实例中对应的值都打印出来:

    截屏2022-01-11 23.14.46.png

  • 既然mirror可以根据实例对象,获取给对象类型中的成员变量以及对应的值,我们可以使用mirror来将对象转成JSON

对象转JSON

  • 具体代码如下:

    struct WSTeacher {
        var name: String = "wushuang"
        var age: Int = 18
    }
    
    let t = WSTeacher()
    
    func test(_ obj: Any) -> Any {
        let mirror = Mirror(reflecting: obj)
        guard !mirror.children.isEmpty else { return obj }
      
        var keyValue: [String: Any] = [:]
        for child in mirror.children {
            if let key = child.label {
                keyValue[key] = test(child.value)
            } else {
                print(" key is not exist ")
            }
        }
        return keyValue
    }
    
    • 代码主要是根据对象创建mirror对象,然后遍历children中的child,将child中的labelkeyvalue为值存入Dictionary中,如果value也是一个对象,则也走一遍上述流程。最终得到JSON数据如下:

      截屏2022-01-13 00.21.43.png

  • 还可以将遍历的过程写成协议,具体过程如下:

    protocol WSJSONMap {
        func jsonMap() -> Any
    }
    extension WSJSONMap {
        func jsonMap() -> Any {
            let mirror = Mirror(reflecting: self)
            guard !mirror.children.isEmpty else { return self }
          
            var keyValue: [String: Any] = [:]
            for children in mirror.children {
                if let value = children.value as? WSJSONMap {
                    if let key = children.label {
                        keyValue[key] = value.jsonMap()
                    } else {
                        print(" key is not exist ")
                    }
                } else {
                    print(" value is not obey WSJSONMap ")
                }
            }
            return keyValue
        }
    }
    
    struct WSPerson: WSJSONMap {
        var name: String = "wushuang"
        var age: Int = 18
    }
    extension String : WSJSONMap { }
    extension Int : WSJSONMap { }
    
    var ws = WSPerson()
    
    • 只需要结构体继承协议,然后结构体中成员变量的类型遵守协议,就可以调用协议方法,如果成员变量的类型没有遵守协议,则不会写入最终的json中:

    截屏2022-01-13 17.33.59.png

    • 如果key不存在或者value没有遵守协议,就会打印出来错误信息,但我们理想的效果是通过结果直接返回,此时就需要用到错误处理Error

抛出错误

  • 首先定义一个Error类型的枚举:

    enum WSJSONMapError: Error {
        case emptyKey
        case nonComplianceProtocol
    }
    
  • 然后将协议方法中的错误替换成对应的Error类型,如果直接return错误类型,则会混淆是Json返回还是错误返回,所以此时需要使用throw将错误抛出

throw和throws

  • throw:将错误抛出

  • throws:返回值中可能有错误抛出,则返回值需要用throws修饰

  • 修改的具体代码如下:

    截屏2022-01-13 20.57.07.png

    • 由于协议方法返回值可能存在错误,所以调用时需要加上try,也就是错误处理

错误处理

  • 有以下几种错误处理的方式:

try&try?&try!

  • try:只管调用,即使有错误会向抛给上层函数,可以理解为甩锅

    截屏2022-01-13 21.21.02.png

  • try?:返回的是可选类型,调用的结果如下:

    • 没有错误: 返回json
    • 有错误:返回nil

    截屏2022-01-13 21.25.54.png

  • try!:认为代码不会有错误

    截屏2022-01-13 21.28.08.png

这三种方式处理错误,依然会使程序崩溃,所以需要结合do-catch使用

do-catch

  • do-catch结合try遇到错误类型时会在catch打印出具体的错误:

    截屏2022-01-13 21.35.10.png

  • do-catch结合try?遇到错误类型时会在do打印出nil

  • do-catch结合try!遇到错误类型时,依然会崩溃

localizedError

  • 如果想要打印更多的错误信息,则可以使用系统的localizedError,它的Api如下:

    public protocol LocalizedError : Error {
        var errorDescription: String? { get } // 错误描述
        var failureReason: String? { get } // 失败原因
        var recoverySuggestion: String? { get } // 建议
        var helpAnchor: String? { get } // 帮助
    }
    
  • 此时我们选择使用错误描述,代码如下:

    截屏2022-01-13 22.17.59.png

    • 在打印错误时输出error.localizedDescription就可以打印出定义的内容:

    截屏2022-01-13 22.35.18.png

CustomNSError

  • CustomNSError相当于是NSError,它的Api如下:

    public protocol CustomNSError : Error {
    
        static var errorDomain: String { get }
    
        var errorCode: Int { get }
    
        var errorUserInfo: [String : Any] { get }
    }
    
    • 我们可以使自定义的协议继承它,然后根据枚举设置code

    截屏2022-01-13 22.44.38.png

    • do-catch中的错误code打印如下:

    截屏2022-01-13 22.48.19.png

通过对Mirror的案例使用,会产生以下疑问:
1. Mirror是怎么获取实例变量的名字以及它的值?
2. Swift是静态语言,系统做了什么使Swift具有反射特性?

带着疑问,我们去 Swift源码 进行分析

Mirror源码解析

  • 在源码中,有三个文件与Mirror相关的,他们的关系如下:

      1. Mirror.swift:暴露给Swift上层使用的Api,以及一些方法的实现
      1. ReflectionMirror.swiftMirror.swift中的函数调用的是ReflectionMirror.swiftMirror的拓展函数,而Mirror的拓展函数是@_silgen_name包装函数后的匿名函数
      1. ReflectionMirror.cpp@_silgen_name的包装函数都在ReflectionMirror.cpp
  • @_silgen_name作用分析,代码如下:

    // test.c
    int addNum(int a, int b) {
        return a + b;
    }
    
    // main.swift
    @_silgen_name("addNum")
    func wushuangAdd(a: Int, b: Int) ->Int
    
    let result = wushuangAdd(a: 20, b: 10)
    print(result)
    
    • Swift工程中创建test.cc文件,不创建桥接文件,然后在c文件中定义addNum方法,接着在main.swift文件中使用@_silgen_name把字符串addNum包装成swift方法wushuangAdd

    • 运行结果如下:

    截屏2022-01-14 15.54.51.png

    • 结果成功的调用了c中定义的函数,使用桥接文件,然后c文件带.h的情况下也是可以调用的

    • 根据结果可以得出以下结论

    结论Swift工程可以在没有c.h没有桥接文件的条件下,使用@_silgen_name关键字将c中的函数暴露给swift使用

  • 下面从最基本的Api开始分析,首先进入Mirror.swift文件,可以看到Mirror是一个结构体,与上面提到的Api基本一致,那我们直接来看初始化方法:

    public init(reflecting subject: Any) {
        if case let customized as CustomReflectable = subject {
            self = customized.customMirror 
        } else {
            self = Mirror(internalReflecting: subject)
        }
    }
    
    • 如果subject的类型属于CustomReflectable,则生成的mirror由它的customMirror属性决定;反之则由系统创建
  • 然后进入ReflectionMirror.swift文件查看internalReflecting的函数实现:

    截屏2022-01-14 16.33.17.png

    • 函数主要有以下5个步骤:
        1. 获取实际的类型
        1. 获取成员变量的数量
        1. 将成员变量的namevalue组装成child,并放入集合Children
        1. 设置父mirror
        1. 获取对象的displayStyle

下面我们主要去分析前面三个步骤:

一、 获取type

  • 获取type的核心方法是调用_getNormalizedType,而它是swift_reflectionMirror_normalizedType函数的包装函数:

    @_silgen_name("swift_reflectionMirror_normalizedType")
    internal func _getNormalizedType<T>(_: T, type: Any.Type) -> Any.Type
    
    • 所以核心方法实际是ReflectionMirror.cpp中的swift_reflectionMirror_normalizedType函数:
    SWIFT_CC(swift) SWIFT_RUNTIME_STDLIB_API
    const Metadata *swift_reflectionMirror_normalizedType(OpaqueValue *value,
                                                          const Metadata *type,
                                                          const Metadata *T) {
      return call(value, T, type, [](ReflectionMirrorImpl *impl) { return impl->type; });
    }
    
    • 该方法返回的是call函数,参数valuemirror实例,type是对象的类型,T是传入的对象,而类型最终的返回值是impl->type

二、获取count

  • 获取count的方法_getChildCount,实际是调用ReflectionMirror.cpp中的swift_reflectionMirror_count方法:

    @_silgen_name("swift_reflectionMirror_count")
    internal func _getChildCount<T>(_: T, type: Any.Type) -> Int
    
    • swift_reflectionMirror_count的方法中,它调用的也是call方法,然后通过impl->count获得count
    intptr_t swift_reflectionMirror_count(OpaqueValue *value,
                                          const Metadata *type,
                                          const Metadata *T) {
      return call(value, T, type, [](ReflectionMirrorImpl *impl) {
        return impl->count();
      });
    }
    

三、获取成员变量

  • 获得成员变量和上面一样,最终也是调用包装方法:

    @_silgen_name("swift_reflectionMirror_subscript")
    internal func _getChild<T>(
      of: T,
      type: Any.Type,
      index: Int,
      outName: UnsafeMutablePointer<UnsafePointer<CChar>?>,
      outFreeFunc: UnsafeMutablePointer<NameFreeFunc?>
    ) -> Any
    
    • swift_reflectionMirror_subscript方法最终调用的也是call
    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);
      });
    }
    
    • 最后通过impl调用subscript方法来获取成员变量

通过上面的简单分析,我们得到他们的获取都与ReflectionMirrorImpl有关,下面我们去看看它到底是个什么

ReflectionMirrorImpl(反射基类)

  • ReflectionMirrorImpl是反射基类,它是一个结构体:

    截屏2022-01-17 14.15.51.png

    • 结构体里有typecountsubscript等属性和方法

反射的种类

  • 在文件中可以看到ReflectionMirrorImpl有几下几个子类:

      1. TupleImpl:元祖类型的反射
      1. StructImpl:结构体类型的反射
      1. EnumImpl: 枚举的反射
      1. ClassImpl: 类的反射
      1. ObjCClassImplOCClass的反射
      1. MetatypeImpl:元数据
      1. OpaqueImpl:不透明类型

了解了ReflectionMirrorImpl后,我再去分析上面三个步骤的详细过程

获取type

  • 首先进入call函数:

    截屏2022-01-15 10.15.27.png

    • call中,先创建typevalue,然后通过unwrapExistential对他们进行赋值,再赋值给impltype
    • unwrapExistential函数中,又调用了getDynamicType来动态获取type

    截屏2022-01-17 15.19.12.png

    • 实际上获取type的核心步骤都在元数据中进行的,下面我们以结构体为例子去分析
  • 先去看看StructImpl

    截屏2022-01-17 15.27.44.png

    • StructImpl是一个结构体,在里面可以看到获取count,和获取成员变量的方法,但并没有看到type的获取,而获取countisReflectable,是通过getDescription来得到,也许type的获取和getDescription有关。getDescriptionStructMetadata的实例调用的,StructMetadataTargetStructMetadata的别名

    • TargetStructMetadatagetDescription最终调用的是this->Description,而它本身没有这个成员,说明它可能在父类TargetValueMetadata中,

  • 进入TargetValueMetadata中,我们发现了Description

    截屏2022-01-17 16.52.39.png

    • Description是对元数据的描述,起决定作用的是传入的TargetValueTypeDescriptor
  • 在进入TargetValueTypeDescriptor -> TargetTypeContextDescriptor,发现里面有个name,根据注释得知它就是type的名字:

    截屏2022-01-17 16.58.12.png

    • nameTargetRelativeDirectPointer指针,是传入的const char类型
  • 通过进入TargetRelativeDirectPointer -> RelativeDirectPointer

    截屏2022-01-17 17.05.46.png

    • RelativeDirectPointer里提供了父类的指针的获取方法,他们都是调用的是父类的get方法得到的
  • 再进入父类RelativeDirectPointerImpl,它有一个成员变量RelativeOffsetValueTy是传入的值,PointerTy是指针:

    截屏2022-01-17 17.14.42.png

    • get方法核心是调用applyRelativeOffset

    截屏2022-01-17 17.17.33.png

    • applyRelativeOffset的核心代码如下:

    截屏2022-01-17 17.17.41.png

    • applyRelativeOffset主要是获取该内存的base地址,然后offset是结构体相对于base的偏移量,二者相加就得到了它的内存地址
  • 通过分析,我们知道可以得到name,再调用get方法来获取type。但提前要搞清楚内存的结构

    • TargetTypeContextDescriptor继承自TargetContextDescriptorTargetContextDescriptor,有两个成员FlagsParent,都是Int32类型
  • 下面我们用伪代码来验证name是不是我们要的type

代码验证

  • 通过上面分析,可以写出相应的数据类型:

    struct WSMetadata {
        var kind: Int
        var desc: UnsafeMutablePointer<WSMetadataDesc>
    }
    
    struct WSMetadataDesc {
        var flags: Int32
        var parent: Int32
        var name: WSRelativeDirectPointer<CChar>
    }
    
    struct WSRelativeDirectPointer<T> {
        var offSet: Int32
        mutating func get() -> UnsafeMutablePointer<T> {
            let offset = self.offSet
            return withUnsafePointer(to: &self) { ptr in
                return UnsafeMutablePointer(mutating: UnsafeRawPointer(ptr).advanced(by: numericCast(offset)).assumingMemoryBound(to: T.self))
            }
        }
    }
    
  • 然后通过unsafeBitCast函数进行按位强转,将WSPerson结构体的内存强转到WSMetadata

    struct WSPerson {
        var age: Int = 18
        var name: String = "wushuang"
    }
    
    let ptr = unsafeBitCast(WSPerson.self as Any.Type, to: UnsafeMutablePointer<WSMetadata>.self)
    let namePtr = ptr.pointee.desc.pointee.name.get()
    
    print(String(cString: namePtr))
    
    • 打印结果如下:

    截屏2022-01-17 18.53.04.png

count

  • 在上面的StructImpl结构中,我们知道count是通过Description中的NumFields来获取的,而getDescription返回的是TargetStructDescriptor类型,进入类中,于是发现成员NumFieldsFieldOffsetVectorOffset

  • 然后再找到TargetTypeContextDescriptor,发现name还有两个成员变量AccessFunctionPtrFields

    截屏2022-01-17 19.32.57.png

    • 这两个成员变量和name的模版函数是一样的,只是传入的类型不一样

代码验证

  • 搞清楚结构后,再来补充WSMetadataDesc的成员变量,由于AccessFunctionPtrFields传入的类型不确定,可以写成原生指针,具体代码如下:

    struct WSMetadataDesc {
        var flags: Int32
        var parent: Int32
        var name: WSRelativeDirectPointer<CChar>
        var AccessFunctionPtr: WSRelativeDirectPointer<UnsafeRawPointer>
        var Fields: WSRelativeDirectPointer<UnsafeRawPointer>
        var NumFields: Int32
        var FieldOffsetVectorOffset: Int32
    }
    
    • 然后打印NumFields

    截屏2022-01-17 19.46.50.png

    • 改变WSPerson成员数量再打印:

    截屏2022-01-17 19.48.39.png

    • 结果与WSPerson的成员数量完全一致

获取实例变量的名字和值

获取实例变量主要跟StructImpl中的subscript方法有关

截屏2022-01-17 20.22.02.png

  • 从代码中可知,获取实例变量名字调用getFieldAt方法:

    截屏2022-01-18 00.21.50.png

    • 这里主要是先从desc->Fields中获取fields数组,然后根据下标获取其中一个field,最后再从field中获取name
  • 在上面分析中我们知道Field是模版函数TargetRelativeDirectPointer传入类型FieldDescriptor构造来的,下面来看看FieldDescriptor

    截屏2022-01-18 00.33.09.png

    • FieldDescriptor是一个类,有5个成员变量
      • MangledTypeNameSuperclassRelativeDirectPointer模版函数构造,且传入的都是const char类型
      • KindFieldRecordSizeuint16_t类型
      • NumFieldsuint32_t类型
    • getFields方法中,可以看出它是一片连续的内存,存储FieldRecord类型的数据

    截屏2022-01-18 00.50.13.png

    • 再查看FieldRecord的数据类型:

    截屏2022-01-18 00.53.37.png

    • 它有三个成员,而FieldName就是我们要找的成员变量的名字
  • 下面将WSMetadataDesc的结构体继续改写:

    struct WSMetadata {
        var kind: Int
        var desc: UnsafeMutablePointer<WSMetadataDesc>
    }
    
    struct WSMetadataDesc {
        var flags: Int32
        var parent: Int32
        var name: WSRelativeDirectPointer<CChar>
        var AccessFunctionPtr: WSRelativeDirectPointer<UnsafeRawPointer>
        var Fields: WSRelativeDirectPointer<WSFieldDescriptor>
        var NumFields: Int32
        var FieldOffsetVectorOffset: Int32
    }
    
    struct WSFieldDescriptor {
        var MangledTypeName: WSRelativeDirectPointer<CChar>
        var Superclass: WSRelativeDirectPointer<CChar>
        var Kind: Int16
        var FieldRecordSize: Int16
        var NumFields: Int32
        var fields: WSFieldRecord // 一块连续的内存,存储的都是 WSFieldRecord 类型数据
    }
    
    struct WSFieldRecord {
        var Flags: Int32
        var MangledTypeName: WSRelativeDirectPointer<CChar>
        var FieldName: WSRelativeDirectPointer<CChar>
    }
    
    struct WSRelativeDirectPointer<T> {
        var offSet: Int32
        mutating func get() -> UnsafeMutablePointer<T> {
            let offset = self.offSet
            return withUnsafePointer(to: &self) { ptr in
                return UnsafeMutablePointer(mutating: UnsafeRawPointer(ptr).advanced(by: numericCast(offset)).assumingMemoryBound(to: T.self))
            }
        }
    }
    
  • 调用方法如下:

    struct WSPerson {
      var age: Int = 18
      var name: String = "wushuang"
      var height: CGFloat = 185.0
      var hobby: String = "basketball"
      var nickName: String = "ws"
    }
    // 指向 WSMetadata的指针
    let ptr = unsafeBitCast(WSPerson.self as Any.Type, to: UnsafeMutablePointer<WSMetadata>.self) 
    
    // 获取指向 fields 的首地址
    var fieldsDesPtr = ptr.pointee.desc.pointee.Fields.get()
    let recordPtr = withUnsafePointer(to: &fieldsDesPtr.pointee.fields) { ptr in
        return UnsafeMutablePointer(mutating: UnsafeRawPointer(ptr).assumingMemoryBound(to: WSFieldRecord.self))
    }
    
    // 获取成员变量的名字的方式
    let age = recordPtr.pointee.FieldName.get()
    print(String(cString: age))
    
     let name = (recordPtr + 1).pointee.FieldName.get()
    print(String(cString: name))
    
    let height = recordPtr[2].FieldName.get()
    print(String(cString: height))
    
    let hobby = recordPtr.advanced(by: 3).pointee.FieldName.get()
    print(String(cString: hobby))
    
    • 打印成员变量主要有三个步骤:
        1. 获取指向WSMetadata的指针
        1. 获取指向连续内存fields的首地址
        1. 根据内存平移获取每个成员变量的field,再获取对应的FieldName

总结

  • mirror在反射时,通过反射对象的实例来初始化一个struct类型数据,这个数据的内存结构与反射对象对应的类是一致的,然后通过访问对应内存的位置来读取相应的typecount,以及成员变量信息