iOS之swift中的反射Mirror

434 阅读3分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

反射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)")
}

2251862-08a0dcd55fd23813.jpg 查看Mirror定义

  • 进入Mirror初始化方法,发现传入的类型是Any,则可以直接传t
public init(reflecting subject: Any)
  • 进入children
public let children: Mirror.Children
👇
//进入Children,发现是一个AnyCollection,接收一个泛型
public typealias Children = AnyCollection<Mirror.Child>
👇
//进入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"
}

<!--JSON解析-->
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

2251862-10746f5468a28216.jpg 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协议

2251862-c4df0c4527da93a2.jpg 【修改】 :所以在原有基础上增加以下代码

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

修改后的运行结果如下

2251862-67af046a583b9d1c.jpg