swift 进阶:反射机制Mirror & 错误处理

swift 进阶之路:学习大纲

内容

    1. 反射机制Mirror
    1. 错误处理
    1. Mirror源码解析

1、 反射Mirror

反射:是指可以动态获取类型、成员信息,在运行时可以调用方法、属性等行为的特性,

  • 在上面的分析中,我们已经知道,对于一个纯swift类来说,并不支持直接像OC runtime那样的操作

  • 但是swift标准库依旧提供了反射机制,用来访问成员信息,即Mirror

一般使用

class CJLTeacher: NSObject {
    var age: Int = 18
}
let mirror = Mirror(reflecting: CJLTeacher.self)
for pro in mirror.children{
    print("\(pro.label): \(pro.value)")
}

  • 运行上面代码,发现没有任何打印,为什么?是因为Mirror中传入的参数不对,应该是传入实例对象,修改如下
class CJLTeacher: NSObject {
    var age: Int = 18
}
var t = CJLTeacher()
//传入t也可以
let mirror = Mirror(reflecting: t.self)
for pro in mirror.children{
    print("\(pro.label): \(pro.value)")
}

image

查看Mirror定义

  • 进入Mirror初始化方法,发现传入的类型是Any,则可以直接传t
public init(reflecting subject: Any)

  • 进入children
public let children: Mirror.Children

//进入Children,发现是一个AnyCollection,接收一个泛型
public typealias Children = AnyCollection

//进入Child,发现是一个元组类型,由可选的标签和值构成,
public typealias Child = (label: String?, value: Any)

这也是为什么能够通过labelvalue打印的原因。即可以在编译时期且不用知道任何类型信息情况下,在Child的值上用Mirror遍历整个对象的层级视图

JSON解析

根据Mirror的这个特性,我们思考下,可以通过Mirror做什么?首先想到的是JSON解析,如下所示,我们定义了一个CJLTeacher类,然后通过一个test方法来解析t

class CJLTeacher {
    var age = 18
    var name = "CJL"
}


func test(_ obj: Any) -> Any{
    let mirror = Mirror(reflecting: obj)
    //判断条件 - 递归终止条件
    guard !mirror.children.isEmpty else {
        return obj
    }
    //字典
    var keyValue: [String: Any] = [:]
    //遍历
    for children in mirror.children {
        if let keyName = children.label {
            //递归调用
            keyValue[keyName] = test(children.value)
        }else{
            print("children.label 为空")
        }
    }
    return keyValue
}


var t = CJLTeacher()
print(test(t))

代码的打印结果如下,打印出了key-value

image

JSON解析封装
如果我们想大规模的使用上述的JSON解析,上面只是针对CJLTeacher的JSON解析,所以,为了方便其他类使用,可以将JSON解析抽取成一个协议,然后提供一个默认实现,让类遵守协议

//1、定义一个JSON解析协议
protocol CustomJSONMap {
    func jsonMap() -> Any
}
//2、提供一个默认实现
extension CustomJSONMap{
    func jsonMap() -> Any{
        let mirror = Mirror(reflecting: self)
        //递归终止条件
        guard !mirror.children.isEmpty else {
            return self
        }
        //字典,用于存储json数据
        var keyValue: [String: Any] = [:]
        //遍历
        for children in mirror.children {
            if let value = children.value as? CustomJSONMap {
                if let keyName = children.label {
                    //递归
                    keyValue[keyName] = value.jsonMap()
                }else{
                    print("key是nil")
                }
            }else{
                print("当前-\(children.value)-没有遵守协议")
            }
        }
        return keyValue
    }
}

//3、让类遵守协议(注意:类中属性的类型也需要遵守协议,否则无法解析)
class CJLTeacher: CustomJSONMap {
    var age = 18
    var name = "CJL"
}

//使用
var t = CJLTeacher()
print(t.jsonMap())

【问题】:运行代码发现,并不是我们想要的结果,原因是因为CJLTeacher中属性的类型也需要遵守CustomJSONMap协议

image

【修改】:所以在原有基础上增加以下代码

//Int、String遵守协议
extension Int: CustomJSONMap{}
extension String: CustomJSONMap{}

修改后的运行结果如下

image

2、 错误处理

为了让我们自己封装的JSON解析更好用,除了对正常返回的处理,还需要对其中的错误进行处理,在上面的封装中,我们目前采用的是print打印的,这样并不规范,也不好维护及管理。那么如何在swift中正确的表达错误呢?

首先,Swift中,提供了Error协议标识当前应用程序发生错误的情况,其中Error的定义如下

public protocol Error {
}

Error是一个空协议,其中没有任何实现,这也就意味着你可以遵守该协议,然后自定义错误类型。所以不管是我们的struct、Class、enum,我们都可以遵循这个Error来表示一个错误

所以接下来,对我们上面封装的JSON解析修改其中的错误处理

  • 定义一个JSONMapError错误枚举,将默认实现的print替换成枚举类型
//定义错误类型
enum JSONMapError: Error{
    case emptyKey
    case notConformProtocol
}

//1、定义一个JSON解析协议
protocol CustomJSONMap {
    func jsonMap() -> Any
}
//2、提供一个默认实现
extension CustomJSONMap{
    func jsonMap() -> Any{
        let mirror = Mirror(reflecting: self)
        //递归终止条件
        guard !mirror.children.isEmpty else {
            return self
        }
        //字典,用于存储json数据
        var keyValue: [String: Any] = [:]
        //遍历
        for children in mirror.children {
            if let value = children.value as? CustomJSONMap {
                if let keyName = children.label {
                    //递归
                    keyValue[keyName] = value.jsonMap()
                }else{
                    return JSONMapError.emptyKey
                }
            }else{
                return JSONMapError.notConformProtocol
            }
        }
        return keyValue
    }
}

  • 但是这里有一个问题,jsonMap方法的返回值是Any,我们无法区分返回的是错误还是json数据,那么该如何区分呢?即如何抛出错误呢?在这里可以通过throw关键字(即将Demo中原本return的错误,改成throw抛出)
//2、提供一个默认实现
extension CustomJSONMap{
    func jsonMap() -> Any{
        let mirror = Mirror(reflecting: self)
        //递归终止条件
        guard !mirror.children.isEmpty else {
            return self
        }
        //字典,用于存储json数据
        var keyValue: [String: Any] = [:]
        //遍历
        for children in mirror.children {
            if let value = children.value as? CustomJSONMap {
                if let keyName = children.label {
                    //递归
                    keyValue[keyName] = value.jsonMap()
                }else{
                    throw JSONMapError.emptyKey
                }
            }else{
                throw JSONMapError.notConformProtocol
            }
        }
        return keyValue
    }
}

  • 发现改成throw抛出错误后,编译器提示有错,其原因是因为方法并没有声明成throws

    image
  • 所以还需要在方法的返回值箭头前增加throws(表示将方法中错误抛出),修改后的默认实现代码如下所示

//1、定义一个JSON解析协议
protocol CustomJSONMap {
    func jsonMap() throws-> Any
}
//2、提供一个默认实现
extension CustomJSONMap{
    func jsonMap() throws -> Any{
        let mirror = Mirror(reflecting: self)
        //递归终止条件
        guard !mirror.children.isEmpty else {
            return self
        }
        //字典,用于存储json数据
        var keyValue: [String: Any] = [:]
        //遍历
        for children in mirror.children {
            if let value = children.value as? CustomJSONMap {
                if let keyName = children.label {
                    //递归
                    keyValue[keyName] = value.jsonMap()
                }else{
                    throw JSONMapError.emptyKey
                }
            }else{
                throw JSONMapError.notConformProtocol
            }
        }
        return keyValue
    }
}

  • 由于我们在jsonMap方法中递归调用了自己,所以还需要在递归调用前增加 try 关键字
//2、提供一个默认实现
extension CustomJSONMap{
    func jsonMap() throws -> Any{
        let mirror = Mirror(reflecting: self)
        //递归终止条件
        guard !mirror.children.isEmpty else {
            return self
        }
        //字典,用于存储json数据
        var keyValue: [String: Any] = [:]
        //遍历
        for children in mirror.children {
            if let value = children.value as? CustomJSONMap {
                if let keyName = children.label {
                    //递归
                    keyValue[keyName] = try value.jsonMap()
                }else{
                    throw JSONMapError.emptyKey
                }
            }else{
                throw JSONMapError.notConformProtocol
            }
        }
        return keyValue
    }
}


var t = CJLTeacher()
print(try t.jsonMap())

到此,一个完整的错误表达方式就完成了

swift中错误处理的方式

swift中错误处理的方式主要有以下两种:

  • 【方式一】:使用try关键字,是最简便的,即甩锅,将这个抛出给别人(向上抛出,抛给上层函数)。但是在使用时,需要注意以下两点:
    • try? 返回一个可选类型,只有两种结果:

      • 要么成功,返回具体的字典值

      • 要么错误,但并不关心是哪种错误,统一返回nil

    • try! 表示你对这段代码有绝对的自信,这行代码绝对不会发生错误

  • 【方式二】:使用do...catch

【方式一】try

通过try来处理JSON解析的错误

//CJLTeacher中定义一个height属性,并未遵守协议
class CJLTeacher: CustomJSONMap {
    var age = 18
    var name = "CJL"
    var height = 1.85
}

/*****1、try? 示例*****/
var t = CJLTeacher()
print(try? t.jsonMap())

/*****打印结果*****/
nil

/*****2、try! 示例*****/
var t = CJLTeacher()
print(try! t.jsonMap())

/*****打印结果*****/
Fatal error: 'try!' expression unexpectedly raised an error: _5_MirrorAndError.JSONMapError.notConformProtocol: file _5_MirrorAndError/main.swift, line 90
2020-12-20 18:27:28.305112+0800 05-MirrorAndError[18642:1408258] Fatal error: 'try!' expression unexpectedly raised an error: _5_MirrorAndError.JSONMapError.notConformProtocol: file _5_MirrorAndError/main.swift, line 90


var t = CJLTeacher()
try t.jsonMap()

/*****打印结果*****/
Fatal error: Error raised at top level: _5_MirrorAndError.JSONMapError.notConformProtocol: file Swift/ErrorType.swift, line 200
2020-12-20 18:31:24.837476+0800 05-MirrorAndError[18662:1410970] Fatal error: Error raised at top level: _5_MirrorAndError.JSONMapError.notConformProtocol: file Swift/ErrorType.swift, line 200

从上面可以知道,错误是向上抛出的,即抛给了上层函数,如果上层函数也不处理,则直接抛给main,main没有办法处理,则直接报错,例如下面的例子

//使用
var t = CJLTeacher()

func test() throws -> Any{
    try t.jsonMap()
}
try test()

其运行结果如下

image

方式二:do-catch

通过do-catch来处理JSON解析的错误

var t = CJLTeacher()
do{
    try t.jsonMap()
}catch{
    print(error)
}

运行结果如下

image

LocalError协议

如果只是用Error还不足以表达更详尽的错误信息,可以使用LocalizedError协议,其定义如下

public protocol LocalizedError : Error {

    /// A localized message describing what error occurred.错误描述
    var errorDescription: String? { get }

    /// A localized message describing the reason for the failure.失败原因
    var failureReason: String? { get }

    /// A localized message describing how one might recover from the failure.建议
    var recoverySuggestion: String? { get }

    /// A localized message providing "help" text if the user requests help.帮助
    var helpAnchor: String? { get }
}

  • 继续修改上面的JSON解析,对JSONMapError枚举增加一个扩展,遵守LocalizedError协议,可以打印更详细的错误信息
//定义更详细的错误信息
extension JSONMapError: LocalizedError{
    var errorDescription: String?{
        switch self {
        case .emptyKey:
            return "key为空"
        case .notConformProtocol:
            return "没有遵守协议"
        }
    }
}


var t = CJLTeacher()
do{
    try t.jsonMap()
}catch{
    print(error.localizedDescription)
}

运行结果如下

image

CustomNSError协议

CustomNSError相当于OC中的NSError,其定义如下,有三个默认属性

public protocol CustomNSError : Error {

    /// The domain of the error.
    static var errorDomain: String { get }

    /// The error code within the given domain.
    var errorCode: Int { get }

    /// The user-info dictionary.
    var errorUserInfo: [String : Any] { get }
}

  • 继续修改JSON解析中的JSONMapError,让其遵守CustomNSError协议,如下所示
//CustomNSError相当于NSError
extension JSONMapError: CustomNSError{
    var errorCode: Int{
        switch self {
        case .emptyKey:
            return 404
        case .notConformProtocol:
            return 504
        }
    }
}


var t = CJLTeacher()
do{
    try t.jsonMap()
}catch{
    print((error as? JSONMapError)?.errorCode)
}

运行结果如下

image

总结

  • Error是swift中错误类型的基本协议,其中LocalizedErrorCustomNSError都遵守Error

  • 如果在方法中,想要同时返回正常值错误,需要通过throw返回错误,并且在方法返回值的箭头前面加throws关键字,再调用方法时,还需要加上try关键字,或者使用do-catch,使用try时,有以下两点需要注意:

    • try? 返回的是一个可选类型,要么成功,返回正常值,要么失败,返回nil

    • try! 表示你对自己的代码非常自信,绝对不会发生错误,一旦发生错误,就会崩溃

    • 使用建议:建议使用try?,而不是try!

最后附上完整的自定义JSON解析代码

//定义错误类型
enum JSONMapError: Error{
    case emptyKey
    case notConformProtocol
}

//定义更详细的错误信息
extension JSONMapError: LocalizedError{
    var errorDescription: String?{
        switch self {
        case .emptyKey:
            return "key为空"
        case .notConformProtocol:
            return "没有遵守协议"
        }
    }
}

//CustomNSError相当于NSError
extension JSONMapError: CustomNSError{
    var errorCode: Int{
        switch self {
        case .emptyKey:
            return 404
        case .notConformProtocol:
            return 504
        }
    }
}

//1、定义一个JSON解析协议
protocol CustomJSONMap {
    func jsonMap() throws-> Any
}
//2、提供一个默认实现
extension CustomJSONMap{
    func jsonMap() throws -> Any{
        let mirror = Mirror(reflecting: self)
        //递归终止条件
        guard !mirror.children.isEmpty else {
            return self
        }
        //字典,用于存储json数据
        var keyValue: [String: Any] = [:]
        //遍历
        for children in mirror.children {
            if let value = children.value as? CustomJSONMap {
                if let keyName = children.label {
                    //递归
                    keyValue[keyName] = try value.jsonMap()
                }else{
                    throw JSONMapError.emptyKey
                }
            }else{
                throw JSONMapError.notConformProtocol
            }
        }
        return keyValue
    }
}

extension Int: CustomJSONMap{}
extension String: CustomJSONMap{}

//3、让类遵守协议(注意:类中属性的类型也需要遵守协议,否则无法解析)
class CJLTeacher: CustomJSONMap {
    var age = 18
    var name = "CJL"
    var height = 1.85
}

//使用
var t = CJLTeacher()

do{
    try t.jsonMap()
}catch{
    print((error as? JSONMapError)?.errorCode)
}

3、 Mirror源码解析

本文主要是分析Mirror的底层实现,以及根据Mirror底层原理仿写其结构的实现

在Swift-进阶 06:反射Mirror & 错误处理文章中,我们介绍了Mirror的使用,即JSON解析,对此我们有如下一些疑问:

  • 1、系统是如何通过Mirror获取对应的属性以及值的?

  • 2、Swift众所周知是一门静态语言,系统在底层到底做了什么,使swift具有了反射的特性呢?

下面我们来对Mirror的底层实现进行探索

Mirror底层源码分析

反射的API主要是由两部分实现的

  • 一部分是通过Swift实现,即ReflectionMirror.swift

  • 一部分是通过C++实现,即ReflectionMirror.mm

  • 两者之间是通过暴露给swift的C++函数进行通信,即@_silgen_name修饰符会通知swift编译器将这个swift函数映射成C++函数的符号

  • Mirror的源码是在Mirror.swift文件中,路径为swift->stdlib->public->core->Mirror.swift

swift 使用技巧

使用@_silgen_name关键字声明的方法,实际调用是括号中的方法,例如swift_cjl_add实际调用的是c中的cjl_add

  • 通过C定义一个方法,在swift中使用

//.h声明
int cjl_add(int a, int b);
//.c中实现
int cjl_add(int a, int b){
    return a+b;
}


#import "test.h"


var value = cjl_add(10, 20)
print(value)


30

  • 可以将上述代码中的第2步去掉删除,采用@_silgen_name关键字

//通过@_silgen_name声明
@_silgen_name("cjl_add")
func swift_cjl_add(_ a: Int32, _ b: Int32) -> Int32


var value = swift_cjl_add(20, 30)
print(value)


50

分析Mirror

  • Mirror.swift文件中找到Mirror,是一个结构体类型

    image
  • 查找其初始化方法public init(reflecting subject: Any)

public init(reflecting subject: Any) {
    //判断 subject 是否符合 CustomReflectable动态类型
    if case let customized as CustomReflectable = subject {
      //如果符合,则由 customMirror 确定属性
      self = customized.customMirror
    } else {
      //如果不符合,则由语言生成
      self = Mirror(internalReflecting: subject)
    }
}

  • 查找internalReflecting方法(路径为swift->stdlib->public->core->ReflectionMirror.swift
extension Mirror {
  // Mirror的初始化器中检索需要的信息
  /*
  - subject 将要被反射的值
  - subjectType 将要被反射的subject值的类型,通常是值的运行时类型
  - 
  */ 
  internal init(internalReflecting subject: Any,
              subjectType: Any.Type? = nil,
              customAncestor: Mirror? = nil)
  {
    //根据_getNormalizedType获取传入的subject的真正类型,其中type(of: subject)获取的动态类型
    let subjectType = subjectType ?? _getNormalizedType(subject, type: type(of: subject))
    // 获取属性大小
    let childCount = _getChildCount(subject, type: subjectType)
    // 遍历,将属性存储到字典中
    let children = (0 ..< childCount).lazy.map({
      // getChild函数时C++的_getChild 函数的简单封装,将标签名字中包含的C字符串转换为Swift字符串
      getChild(of: subject, type: subjectType, index: $0)
    })
    // 赋值给Mirror的属性children
    self.children = Children(children)
    // 设置父类反射
    self._makeSuperclassMirror = {//按需求构建父类的Mirror的闭包
      // 获取传入对象的类
      guard let subjectClass = subjectType as? AnyClass,
            // 获取父类
            let superclass = _getSuperclass(subjectClass) else {
        return nil//非类的类型、没有父类的类的Mirror,会获取到nil
      }

      // 调用者可以用一个可作为父类的Mirror直接返回Mirror实例来指定自定义的祖先的表现
      // Handle custom ancestors. If we've hit the custom ancestor's subject type,
      // or descendants are suppressed, return it. Otherwise continue reflecting.
      if let customAncestor = customAncestor {
        if superclass == customAncestor.subjectType {
          return customAncestor
        }
        if customAncestor._defaultDescendantRepresentation == .suppressed {
          return customAncestor
        }
      }
      // 给相同值返回一个将 superclass作为 subjectType的新的Mirror
      return Mirror(internalReflecting: subject,
                    subjectType: superclass,
                    customAncestor: customAncestor)
    }

    // 获取并解析显示的样式,并设置Mirror的其他属性
    let rawDisplayStyle = _getDisplayStyle(subject)
    switch UnicodeScalar(Int(rawDisplayStyle)) {
    case "c": self.displayStyle = .class
    case "e": self.displayStyle = .enum
    case "s": self.displayStyle = .struct
    case "t": self.displayStyle = .tuple
    case "\0": self.displayStyle = nil
    default: preconditionFailure("Unknown raw display style '\(rawDisplayStyle)'")
    }

    self.subjectType = subjectType
    self._defaultDescendantRepresentation = .generated
  }
  // 快速查找
  internal static func quickLookObject(_ subject: Any) -> _PlaygroundQuickLook? {
#if _runtime(_ObjC)
    let object = _getQuickLookObject(subject)
    return object.flatMap(_getClassPlaygroundQuickLook)
#else
    return nil
#endif
  }
}



public var superclassMirror: Mirror? {
    return _makeSuperclassMirror()
}
nternal let _makeSuperclassMirror: () -> Mirror?

1、通过_getNormalizedType获取传入的subject的真正类型
2、通过_getChildCount方法获取类中的属性个数
3、遍历属性,通过getChild方法(C++的_getChild 函数的简单封装)将标签名字中包含的C字符串转换为Swift字符串,并将属性存储到字典中,赋值给Mirror的属性children
4、Mirror有一个属性superclassMirror,会返回该类的父类,其底层是返回一个_makeSuperclassMirror属性,用于保存父类的Mirror闭包。首先会通过subjectType获取父类,然后按照需求构建父类的Mirror闭包,如果是非类的类型、没有父类的类的Mirror,会获取到nil。反之,则直接返回一个可作为父类的Mirror的实例对象。
5、获取并解析显示的样式,并设置Mirror的其他属性

  • 进入_getNormalizedType的实现,根据定义,最终会调用C++中的swift_reflectionMirror_normalizedType -> Call方法
@_silgen_name("swift_reflectionMirror_normalizedType")
internal func _getNormalizedType(_: T, type: Any.Type) -> Any.Type

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; });
}

/*
- passedValue 实际需要传入的swift的值的指针
- T 该值的静态类型
- passedType 被显式传入且会用在反射过程中的类型
- f 传递被查找到的会被调用的实现的对象引用
- 返回值:返回f参数调用时的返回值
*/ 
template
auto call(OpaqueValue *passedValue, const Metadata *T, const Metadata *passedType,
          const F &f) -> decltype(f(nullptr))
{
  // 获取type
  const Metadata *type;
  OpaqueValue *value;
  std::tie(type, value) = unwrapExistential(T, passedValue);
  // 判断传入type是否为空,如果不为空,则直接赋值给type
  if (passedType != nullptr) {
    type = passedType;
  }
  // 使用 ReflectionMirrorImpl 子类的实例去结束调用f,然后会调用这个实例上的方法去真正的工作完成
  auto call = [&](ReflectionMirrorImpl *impl) {
    // 返回的type是传入非type
    impl->type = type;
    impl->value = value;
    auto result = f(impl);
    return result;
  };

  .....

  switch (type->getKind()) {
    case MetadataKind::Tuple: {//元组
        ......
    }
    case MetadataKind::Struct: {//结构体
       ......
    }
    case MetadataKind::Enum://枚举
    case MetadataKind::Optional: {//可选
     ......
    }

    ......
}



static std::tuple
unwrapExistential(const Metadata *T, OpaqueValue *Value) {
  // If the value is an existential container, look through it to reflect the
  // contained value.如果该值是一个存在的容器,请查看它以反映包含的值。
  // TODO: Should look through existential metatypes too, but it doesn't
  // really matter yet since we don't have any special mirror behavior for
  // concrete metatypes yet.
  while (T->getKind() == MetadataKind::Existential) {
    auto *existential
      = static_cast(T);

    // Unwrap the existential container.打开存在容器
    T = existential->getDynamicType(Value);
    Value = existential->projectValue(Value);

    // Existential containers can end up nested in some cases due to generic
    // abstraction barriers.  Repeat in case we have a nested existential.
  }
  return std::make_tuple(T, Value);
}



template<> const Metadata *
ExistentialTypeMetadata::getDynamicType(const OpaqueValue *container) const {
// 根据 获取此存在类型使用的表示形式 判断
  switch (getRepresentation()) {
  case ExistentialTypeRepresentation::Class: {
    auto classContainer =
      reinterpret_cast(container);
    void *obj = classContainer->Value;
    return swift_getObjectType(reinterpret_cast(obj));
  }
  case ExistentialTypeRepresentation::Opaque: {
    auto opaqueContainer =
      reinterpret_cast(container);
    return opaqueContainer->Type;
  }
  case ExistentialTypeRepresentation::Error: {
    const SwiftError *errorBox
      = *reinterpret_cast(container);
    return errorBox->getType();
  }
  }

  swift_runtime_unreachable(
      "Unhandled ExistentialTypeRepresentation in switch.");
}

call中主要是一个大型switch声明和一些额外的代码去处理特殊的情况,主要是会ReflectionMirrorImpl子类实例去结束调用 f,然后会调用这个实例上的方法去让真正的工作完成。

1、首先根据unwrapExistential获取type类型,其依赖于metaData元数据
2、判断传入的passedType是否为空,如果不为空,则直接赋值给type
3、使用 ReflectionMirrorImpl子类的实例去结束调用f,然后会调用这个实例上的方法去真正的工作完成

swift-source源码调试_getNormalizedType方法

  • 运行swift源码,在Call函数的第一行加断点
  • 在源码终端依次输入下面代码
class CJLTeacher{var age = 18}
var t = CJLTeacher()
let mirror = Mirror(reflecting: t)

运行结果如下,会在call的调用处断住

image

其中参数的调试结果如下:

  • 其中valueMirror实例

  • typetype(of:)获取的类型

  • T是自己传入的类型

    image

ReflectionMirrorImpl 反射基类

ReflectionMirrorImpl的种类主要有以下几种:

  • TupleImpl 元组的反射

  • StructImpl 结构体的反射

  • EnumImpl 枚举的反射

  • ClassImpl 类的反射

  • MetatypeImpl 元数据的反射

  • OpaqueImpl 不透明类型的反射

这里主要以Struct的反射为例

  • 首先查看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;
  //获取枚举的case名字
  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() {}
};

  • 进入StructImpl结构体的底层实现,需要注意一下几点:
// Implementation for structs.
// ReflectionMirrorImpl 的子类 StructImpl 结构体反射
struct StructImpl : ReflectionMirrorImpl {
  bool isReflectable() {//是否支持反射
    const auto *Struct = static_cast(type);
    const auto &Description = Struct->getDescription();
    return Description->isReflectable();
  }
  // 用 s 的显式样式来表明这是一个结构体
  char displayStyle() {
    return 's';
  }

  intptr_t count() {
    if (!isReflectable()) {
      return 0;
    }
// 首先也是找到metadata,然后通过metadata找到desc,然后找到fields,即 NumFields 记录属性的count
    auto *Struct = static_cast(type);
    return Struct->getDescription()->NumFields;//属性的count
  }

  intptr_t childOffset(intptr_t i) {
    auto *Struct = static_cast(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];
  }

  const FieldType childMetadata(intptr_t i, const char **outName,
                                void (**outFreeFunc)(const char *)) {
    StringRef name;
    FieldType fieldInfo;
    //通过getFieldAt获取属性的名称
    std::tie(name, fieldInfo) = getFieldAt(type, i);
    assert(!fieldInfo.isIndirect() && "indirect struct fields not implemented");

    *outName = name.data();
    *outFreeFunc = nullptr;

    return fieldInfo;
  }
// subscript 用来获取当前属性的名称和值
  AnyReturn subscript(intptr_t i, const char **outName,
                      void (**outFreeFunc)(const char *)) {
    // 获取metaadata
    auto fieldInfo = childMetadata(i, outName, outFreeFunc);

    auto *bytes = reinterpret_cast(value);
    // 获取属性的偏移值
    auto fieldOffset = childOffset(i);
    // 计算字段存储的指针
    auto *fieldData = reinterpret_cast(bytes + fieldOffset);

    return copyFieldContents(fieldData, fieldInfo);
  }
};

1、count方法中属性个数的获取,是通过metadata,然后找到其desc,然后找到NumFields获取的,即NumFields 记录属性的count
2、subscript方法主要用来获取当前属性的名称和值

  • 首先获取metadata
  • 然后获取属性的偏移值fieldOffset
  • 通过首地址+偏移值,计算属性存储的指针

其他几种的分析类似,这里不再作说明

仿写Mirror结构

以上源码说了这么多,是不是还是有点难以理解,下面我们通过仿写底层的结构来帮助理解Mirror获取属性和值的原理

TargetStructMetadata结构体:struct的反射类

  • 从Struct的反射类StructImpl中可以知道,其type的类型是StructMetadata

    image
  • Metadata.h文件中搜索StructMetadata,其真正的类型是TargetStructMetadata

using StructMetadata = TargetStructMetadata;

  • TargetStructMetadata -> TargetValueMetadata结构体,而
    TargetValueMetadata继承自TargetMetadata,在Swift-进阶 02:类、对象、属性文章中,我们已经知道,TargetMetadata中有一个属性kind(相当于OC中的isa),而TargetValueMetadata除了拥有父类的kind,还有一个description,用于记录元数据的描述
/// The common structure of metadata for structs and enums.
template 
struct TargetValueMetadata : public TargetMetadata {
  using StoredPointer = typename Runtime::StoredPointer;
  TargetValueMetadata(MetadataKind Kind,
                      const TargetTypeContextDescriptor *description)
      : TargetMetadata(Kind), Description(description) {}

    //用于记录元数据的描述
  /// An out-of-line description of the type.
  TargetSignedPointer * __ptrauth_swift_type_descriptor> Description;

    ......
}

TargetValueTypeDescriptor类:记录metadata信息

  • 由上面可知,Description的类型是TargetValueTypeDescriptor,其中有两个属性
    • NumFields 用于记录属性的count
    • FieldOffsetVectorOffset 用于记录属性在metadata中便宜向量的偏移量
template 
class TargetStructDescriptor final
    : public TargetValueTypeDescriptor,
      public TrailingGenericContextObjects,
                            TargetTypeGenericContextDescriptorHeader,
                            /*additional trailing objects*/
                            TargetForeignMetadataInitialization,
                            TargetSingletonMetadataInitialization> {
    ......

    /// The number of stored properties in the struct.
  /// If there is a field offset vector, this is its length.
  uint32_t NumFields;//记录属性的count
  /// The offset of the field offset vector for this struct's stored
  /// properties in its metadata, if any. 0 means there is no field offset
  /// vector.
  uint32_t FieldOffsetVectorOffset;//记录属性在metadata中便宜向量的偏移量

    ......
}

  • 进入其继承链TargetValueTypeDescriptor -> TargetTypeContextDescriptor类,其中有3个属性
    • Name 用于记录类型的名称,标识当前的类型
    • AccessFunctionPtr 指向此类型的metadata访问函数的指针
    • Fields 指向类型的descriptor的指针
template 
class TargetTypeContextDescriptor
    : public TargetContextDescriptor {
public:
  /// The name of the type. 类型的名称
  TargetRelativeDirectPointer Name;

  /// A pointer to the metadata access function for this type.
  /// 指向此类型的元数据访问函数的指针
  /// The function type here is a stand-in. You should use getAccessFunction()
  /// to wrap the function pointer in an accessor that uses the proper calling
  /// convention for a given number of arguments.
  TargetRelativeDirectPointer AccessFunctionPtr;

  /// A pointer to the field descriptor for the type, if any.指向类型的字段描述符的指针
  TargetRelativeDirectPointer Fields;

    ......
}

  • 进入TargetContextDescriptor基类的定义,其中有两个参数
    • Flags 用于表示描述context的标志,包含kind和version
    • Parent 用于表示父类的context,如果是在顶层,则表示没有父类,则为NULL
/// Base class for all context descriptors.
template
struct TargetContextDescriptor {
  /// Flags describing the context, including its kind and format version.
  ContextDescriptorFlags Flags;

  /// The parent context, or null if this is a top-level context.
  TargetRelativeContextPointer Parent;

  ......
}

从上述的分析中我们可以得知,

  • 属性的获取时通过baseDesc->Fields.get();(在ReflectionMirror.mm文件中getFieldAt方法),即是通过Description中的Fields属性获取,所以还需要分析Fields的类型TargetRelativeDirectPointer,其内部的类型是FieldDescriptor

RelativeDirectPointerImpl类:存放offset偏移量

  • TargetRelativeDirectPointer的真正类型是RelativeDirectPointer -> RelativeDirectPointerImpl,RelativeDirectPointerImpl主要用于存放offset偏移量
    • 属性RelativeOffset,用于表示属性的相对偏移值,而不是直接存储地址,如下所示

      image
    • 其中PointerTy、ValueTy就是传入的类型T、T的指针类型

template
class RelativeDirectPointerImpl {
private:
  /// The relative offset of the function's entry point from *this.
  Offset RelativeOffset;

    ......

public:
  using ValueTy = T;//是一个值
  using PointerTy = T*;//是一个指针
}
    //get方法 - 用于获取属性
  PointerTy get() const & {
    // Check for null.检查是否为空
    if (Nullable && RelativeOffset == 0)
      return nullptr;

    // The value is addressed relative to `this`. 值是相对于“this”寻址的
    uintptr_t absolute = detail::applyRelativeOffset(this, RelativeOffset);
    return reinterpret_cast(absolute);
  }
    ......
}



template
static inline uintptr_t applyRelativeOffset(BasePtrTy *basePtr, Offset offset) {
  static_assert(std::is_integral::value &&
                std::is_signed::value,
                "offset type should be signed integer");
// 指针地址
  auto base = reinterpret_cast(basePtr);
  // We want to do wrapping arithmetic, but with a sign-extended
  // offset. To do this in C, we need to do signed promotion to get
  // the sign extension, but we need to perform arithmetic on unsigned values,
  // since signed overflow is undefined behavior.
  auto extendOffset = (uintptr_t)(intptr_t)offset;
  return base + extendOffset;//指针地址+存放的offset(偏移地址) -- 内存平移获取值
}

FieldDescriptor类:存放属性

进入FieldDescriptor类的定义,如下所示

class FieldDescriptor {
  const FieldRecord *getFieldRecordBuffer() const {
    return reinterpret_cast(this + 1);
  }

public:
  const RelativeDirectPointer MangledTypeName;
  const RelativeDirectPointer Superclass;

    ......

  const FieldDescriptorKind Kind;
  const uint16_t FieldRecordSize;
  const uint32_t NumFields;

    ......

  // 获取所有属性,每个属性用FieldRecord封装
   llvm::ArrayRef getFields() const {
    return {getFieldRecordBuffer(), NumFields};
  }

  ......
} 

FieldRecord类:封装属性

进入FieldRecord类,其定义如下

class FieldRecord {
  const FieldRecordFlags Flags;

public:
  const RelativeDirectPointer MangledTypeName;
  const RelativeDirectPointer FieldName;

上面主要分析了结构体通过Mirror获取属性和值涉及的类和结构体,其结构仿写代码如下

/// metadata元数据
struct StructMetadata {
//    (取自类 - TargetMetadata:kind)
    //(继承关系:TargetStructMetadata -> TargetValueMetadata -> TargetMetadata)
    var kind: Int
//    (取自结构体 -  TargetValueMetadata:Description)
    var desc: UnsafeMutablePointer
}

/// metada的描述信息
struct StructMetadataDesc {
//    (取自底层结构体 - TargetContextDescriptor:flags + parent)
    var flags: Int32
    var parent: Int32
//    (取自底层类 - TargetTypeContextDescriptor:name + AccessFunctionPtr + Fields)
    //type的名称
    //相对指针位置的存储
    var name: RelativeDirectPointer
    //补充完整
    var AccessFunctionPtr: RelativeDirectPointer
    //是通过Fields的getFiledName获取属性名称
    var Fields: RelativeDirectPointer
//    (取自底层类 - TargetClassDescriptor:NumFields + FieldOffsetVectorOffset)
    //属性的count
    var NumFields: Int32
    var FieldOffsetVectorOffset: Int32
}

/// 属性的描述信息
//(取自底层类 - FieldDescriptor)
struct FieldDescriptor {
    var MangledTypeName: RelativeDirectPointer
    var Superclass: RelativeDirectPointer
    var Kind: UInt16
    var FieldRecordSize: Int16
    var NumFields: Int32
    //每个属性都是FieldRecord,记录在这个结构体中
    var fields: FieldRecord//数组中是一个连续的存储空间
}

/// 属性封装类
//(取自底层类 - FieldRecord)
struct FieldRecord{
    var Flags: Int32
    var MangledTypeName: RelativeDirectPointer
    var FieldName: RelativeDirectPointer
}

/// 记录offset偏移值
struct RelativeDirectPointer{
    var offset: Int32

    //模拟RelativeDirectPointerImpl类中的get方法 this+offset指针
    mutating func get() -> UnsafeMutablePointer{
        let offset = self.offset
        return withUnsafePointer(to: &self) { p in
            /*
             获得self,变为raw,然后+offset

             - UnsafeRawPointer(p) 表示this
             - advanced(by: numericCast(offset) 表示移动的步长,即offset
             - assumingMemoryBound(to: T.self) 表示假定类型是T,即自己制定的类型
             - UnsafeMutablePointer(mutating:) 表示返回的指针类型
            */
            return UnsafeMutablePointer(mutating: UnsafeRawPointer(p).advanced(by: numericCast(offset)).assumingMemoryBound(to: T.self))
        }
    }
}

其使用如下

  • 定义一个CJLTeacher类
struct CJLTeacher {
    var age = 18
    var name = "CJL"
}

  • 1、首先获取指向metadata的指针
  • 2、然后获取字符串的地址,例如name,并读取内存值
/将t1绑定到StructMetadata(unsafeBitCast-按位强转,非常危险,没有任何校验、没有任何修饰)
//unsafeBitCast - 所有的内存按位转换
//1、先获取指向metadata的指针
let ptr = unsafeBitCast(CJLTeacher.self as Any.Type, to: UnsafeMutablePointer.self)

//2、然后获取字符串的地址
/*
 ptr.pointee 表示StructMetadata
 ptr.pointee.desc.pointee 表示StructMetadataDesc
 ptr.pointee.desc.pointee.name 表示RelativeDirectPointer
 */
let namePtr = ptr.pointee.desc.pointee.name.get()
print(String(cString: namePtr))
//读取内存值
print(ptr.pointee.desc.pointee.NumFields)

  • 3、获取首地址,通过指针移动来获取访问属性的地址,例如输出age属性
//获取首地址
let filedDescriptorPtr = ptr.pointee.desc.pointee.Fields.get()
//指针移动来获取访问属性的地址
let recordPtr = withUnsafePointer(to: &filedDescriptorPtr.pointee.fields) {
    /*
     - UnsafeRawPointer + assumingMemoryBound -- 类型指针
     - advanced 类型指针只需要移动 下标即可
     */
    return UnsafeMutablePointer(mutating: UnsafeRawPointer($0).assumingMemoryBound(to: FieldRecord.self).advanced(by: 0))
}
//输出age属性
print(String(cString: recordPtr.pointee.FieldName.get()))

如果将advanced中的0改成1,输出name

image

总结

所以综上所述,Mirror反射干的事情:

  • 1、Mirror在实例对象的metadata中找到Descriptor

  • 2、在Descriptor

    • 找到name,获取类型(相当于type名称)
    • 找到numFields,获取属性个数
  • 3、找到FieldDescriptor中的fields,来找到对当前属性的描述,然后通过指针移动,获取其他属性
    以上就是整个mirror在底层做的事情,如下所示,以struct为例的一个流程

    image

【补充】

  • swift中的type(of:)dump(t)就是基于Mirror的反射原理来实现的

  • swift中JSON解析的三方库HandyJSON其原理就是Mirror反射的原理,本质就是利用metadata元数据中的descriptor,然后通过字段的访问,做内存的赋值(后续会完整分析HandyJSON)

所以,针对我们开头的两个问题,可以通过上面的Mirror反射的原理来进行解释
1、系统是如何通过Mirror获取对应的属性以及值的?
参考上述的Mirror原理总结

2、Swift众所周知是一门静态语言,系统在底层到底做了什么,使swift具有了反射的特性呢?
Swift 的反射机制是基于一个叫 Mirror 的 struct 来实现的。即为具体的 subject 创建一个 Mirror,然后就可以通过它查询这个对象subject。简单理解就是 Mirror通过meatadata,在其内部创建了一个结构,用于输出metadata中的descriptor

内容参考:https://www.jianshu.com/p/9af291059cdc

你可能感兴趣的:(swift 进阶:反射机制Mirror & 错误处理)