• 反射 Mirror | Swift 动态性


    Mirror是Swift中的反射机制,反射就是可以动态的获取类型以及成员信息,同时也可以在运行时动态的调用方法和属性等。

    1. Mirror 简介

    Mirror是Swift中的反射机制的实现,它的本质是一个结构体。
    创建 Mirror 最简单的方式就是使用 reflecting 构造器:

    public init(reflecting subject: Any)
    

    正如你所见,对象的类型是 Any。这是 Swift 中最通用的类型。Swift 中的任何东西至少都是 Any类型的。这样一来 mirror 就可以兼容 structclassenumTupleArrayDictionaryset 等。

    下面列举了 Mirror 可用的属性 / 方法:

    • let children: Children:对象的子节点。
    • displayStyle: Mirror.DisplayStyle?:对象的展示类型
    • let subjectType: Any.Type:对象的类型
    • func superclassMirror() -> Mirror?:对象父类的 mirror

    displayStyle

    1. public enum DisplayStyle {
    2. case Struct
    3. case Class
    4. case Enum
    5. case Tuple
    6. case Optional
    7. case Collection
    8. case Dictionary
    9. case Set
    10. }

    它会返回 DisplayStyle enum 的其中一种情况。如果你想要对某种不支持的类型进行反射,你会得到一个空的 Optional 值。
    例如:
    正如之前我们知道的,反射只要求对象是 Any 类型,而且Swift 标准库中还有很多类型为 Any 的东西没有被列举在上面的 DisplayStyle enum 中。如果试图反射它们中间的某一个又会发生什么呢?比如 closure。

    1. let closure = { (a: Int) -> Int in return a * 2 }
    2. let aMirror = Mirror(reflecting: closure)
    3. print("\(aMirror)----------\(aMirror.displayStyle)")
    4. //Mirror for (Int) -> Int----------nil

    children

    这会返回一个包含了对象所有的子节点的 AnyForwardCollection。这些子节点不单单限于 Array 或者 Dictionary 中的条目。诸如 struct 或者 class 中所有的属性也是由 AnyForwardCollection 这个属性返回的子节点。AnyForwardCollection 协议意味着这是一个支持遍历的 Collection 类型。

    1. public class Store {
    2. let storesToDisk: Bool = true
    3. }
    4. public class BookmarkStore: Store {
    5. let itemCount: Int = 10
    6. }
    7. public struct Bookmark {
    8. enum Group {
    9. case Tech
    10. case News
    11. }
    12. private let store = {
    13. return BookmarkStore()
    14. }()
    15. let title: String?
    16. let url: NSURL
    17. let keywords: [String]
    18. let group: Group
    19. }
    20. let aBookmark = Bookmark(title: "Appventure", url: NSURL(string: "appventure.me")!, keywords: ["Swift", "iOS", "OSX"], group: .Tech)
    21. let aMirror = Mirror(reflecting: aBookmark)
    22. for case let (label?, value) in aMirror.children {
    23. print (label, value)
    24. }
    25. //输出:
    26. //store TestDemo3.ViewController.BookmarkStore
    27. //title Optional("Appventure")
    28. //url appventure.me
    29. //keywords ["Swift", "iOS", "OSX"]
    30. //group Tech

    SubjectType

    这是对象的类型:

    1. print(aMirror.subjectType)
    2. //输出 : Bookmark
    3. print(Mirror(reflecting: 5).subjectType)
    4. //输出 : Int
    5. print(Mirror(reflecting: "test").subjectType)
    6. //输出 : String
    7. print(Mirror(reflecting: NSNull()).subjectType)
    8. //输出 : NSNull

    SuperclassMirror

    这是我们对象父类的 mirror。如果这个对象不是一个类,它会是一个空的 Optional 值。如果对象的类型是基于类的,你会得到一个新的 Mirror:

    1. // 试试 struct
    2. print(Mirror(reflecting: aBookmark).superclassMirror)
    3. // 输出: nil
    4. // 试试 class
    5. print(Mirror(reflecting: aBookmark.store).superclassMirror)
    6. // 输出: Optional(Mirror for Store)

    2.反射 Mirror 使用

    1.Mirror 转换对象为字典

    • 定义如下对象:

    1. struct Person {
    2. var name: String = "YDW"
    3. var isMale: Bool = true
    4. var birthday: Date = Date()
    5. }
    6. class Animal: NSObject {
    7. private var eat: String = "吃"
    8. var age: Int = 0
    9. var optionValue: String?
    10. }
    11. class Cat: Animal {
    12. var like: [String] = ["mouse", "fish"]
    13. var master = Person()
    14. }
    • 遍历出字典:
    1. func mapDic(mirror: Mirror) -> [String: Any] {
    2. var dic: [String: Any] = [:]
    3. for child in mirror.children {
    4. // 如果没有label就会被抛弃
    5. if let label = child.label {
    6. let propertyMirror = Mirror(reflecting: child.value)
    7. dic[label] = child.value
    8. }
    9. }
    10. // 添加父类属性
    11. if let superMirror = mirror.superclassMirror {
    12. let superDic = mapDic(mirror: superMirror)
    13. for p in superDic {
    14. dic[p.key] = p.value
    15. }
    16. }
    17. return dic
    18. }
    • 使用 Mirror 转换对象并打印结果, 可以看到可以打印出私有属性:
    1. // Mirror使用
    2. let objc = Cat()
    3. let mirror = Mirror(reflecting: objc)
    4. let mirrorDic = mapDic(mirror: mirror)
    5. print(mirrorDic)
    6. // 打印结果
    7. ["master": TestDemo3.ViewController.Person(name: "YDW", isMale: true, birthday: 2022-07-15 03:51:59 +0000), "eat": "吃", "optionValue": nil, "age": 0, "like": ["mouse", "fish"]]
    • 在实际运用中,可以将应用于元组参数传递(比如网络请求传参,传入元组,网络请求时转换为字典),优点:外部使用知道具体传入什么参数,参数更改不影响方法错误。
    1. // 外部参数定义
    2. var params = (title: "name", comment: "Mirror")
    3. // 网络层统一转换为字典,进行网路请求
    4. let paramsDic = mapDic(mirror: Mirror(reflecting: params))
    5. print(paramsDic)
    6. // 打印结果
    7. ["title": "name", "comment": "Mirror"]
    • 需要注意是只能传入基本类型,并且元组参数要命名,如果直接使用(“name”,“Mirror”)则会变成下面这种情况:
    1. // 外部参数定义
    2. var params = ("name","Mirror")
    3. // 网络层统一转换为字典,进行网路请求
    4. let paramsDic = mapDic(mirror: Mirror(reflecting: params))
    5. print(paramsDic)
    6. // 打印
    7. [".1": "Mirror", ".0": "name"]
    1. JSON 解析
    • 定义一个 YDWTeacher 类:
    1. class YDWTeacher {
    2. var age = 18
    3. var name = "YDW"
    4. }
    • 运用 Mirror 来解析,实现代码如下:
    1. // JSON解析
    2. func test(_ obj: Any) -> Any {
    3. let mirror = Mirror(reflecting: obj)
    4. // 判断条件 - 递归终止条件
    5. guard !mirror.children.isEmpty else {
    6. return obj
    7. }
    8. // 字典
    9. var keyValue: [String: Any] = [:]
    10. // 遍历
    11. for children in mirror.children {
    12. if let keyName = children.label {
    13. // 递归调用
    14. keyValue[keyName] = test(children.value)
    15. } else {
    16. print("children.label 为空")
    17. }
    18. }
    19. return keyValue
    20. }
    21. // 使用
    22. var t = YDWTeacher()
    23. print(test(t))
    • 运行代码,打印结果如下:
    ["name": "YDW","age": 18]
    
    • 如果想在工程中大量的使用上述的 JSON 解析,可以将 Mirror JSON 解析抽取成一个协议,然后提供一个默认实现,让类遵守协议,如下:
    1. // 定义JSON解析协议
    2. protocol CustomJSONMap {
    3. func jsonMap() -> Any
    4. }
    5. // 提供默认实现
    6. extension CustomJSONMap{
    7. func jsonMap() -> Any{
    8. let mirror = Mirror(reflecting: self)
    9. // 递归终止条件
    10. guard !mirror.children.isEmpty else {
    11. return self
    12. }
    13. // 字典,用于存储json数据
    14. var keyValue: [String: Any] = [:]
    15. // 遍历
    16. for children in mirror.children {
    17. if let value = children.value as? CustomJSONMap {
    18. if let keyName = children.label {
    19. // 递归
    20. keyValue[keyName] = value.jsonMap()
    21. } else {
    22. print("key是nil")
    23. }
    24. } else {
    25. print("当前-\(children.value)-没有遵守协议")
    26. }
    27. }
    28. return keyValue
    29. }
    30. }
    31. // 让类遵守协议(注意:类中属性的类型也需要遵守协议,否则无法解析)
    32. class YDWTeacher: CustomJSONMap {
    33. var age = 18
    34. var name = "YDW"
    35. }
    36. // 使用
    37. var t = YDWTeacher()
    38. print(t.jsonMap())
    • 运行,可以发现并没达到我们预想的结果,这是因为 YDWTeacher 的属性的类型也需要遵守 CustomJSONMap 协议:
    1. 当前-18-没有遵守协议
    2. 当前-YDW-没有遵守协议
    • 修改如下,这样就可以达到我们预想的结果:
    1. // Int、String遵守协议
    2. extension Int: CustomJSONMap{}
    3. extension String: CustomJSONMap{}
    4. // 打印结果
    5. ["name": "YDW", "age": 18]

    3.错误处理

    • 为了让封装的JSON 解析更加完善,除了对正常返回的处理,还需要对其中的错误进行处理,在上面的封装中,采用的是 print 打印的,这样并不规范,也不好维护及管理。那么如何在 swift 中正确的表达错误呢?
    • Swift 中,提供了 Error 协议来标识当前应用程序发生错误的情况,其中 Error 的定义如下:
    public protocol Error { }
    
    • 可以看到:Error 是一个空协议,其中没有任何实现,这也就意味着可以遵守该协议,然后自定义错误类型,因此不管是 struct、Class、enum,都可以遵循这个 Error 来表示一个错误。
    • 那么上面封装的 JSON 解析修改错误处理,首先定义一个 JSONMapError 错误枚举,将默认实现的 print 替换成枚举类型:
    1. // 定义错误类型
    2. enum JSONMapError: Error{
    3. case emptyKey
    4. case notConformProtocol
    5. }
    6. extension CustomJSONMap {
    7. func jsonMap() throws -> Any {
    8. let mirror = Mirror(reflecting: self)
    9. // 递归终止条件
    10. guard !mirror.children.isEmpty else {
    11. return self
    12. }
    13. // 字典,用于存储json数据
    14. var keyValue: [String: Any] = [:]
    15. // 遍历
    16. for children in mirror.children {
    17. if let value = children.value as? CustomJSONMap {
    18. if let keyName = children.label {
    19. // 递归
    20. keyValue[keyName] = try value.jsonMap()
    21. } else {
    22. throw JSONMapError.emptyKey
    23. }
    24. } else {
    25. throw JSONMapError.notConformProtocol
    26. }
    27. }
    28. return keyValue
    29. }
    30. }
    • 封装完成,使用如下:
    1. // 使用时需要加上try
    2. var t = YDWTeacher()
    3. do{
    4. _ = try t.jsonMap()
    5. }catch JSONMapError.emptyKey{
    6. print("???emptyKey????")
    7. }catch JSONMapError.notConformProtocol{
    8. print("???notConformProtocol?????")
    9. }catch{
    10. }

    4.获取类型,属性个数及其值

    • 定义一个用户类:
    1. class YDWUser {
    2. var name: String = ""
    3. var nickName: String?
    4. var age: Int?
    5. var emails: [String]?
    6. }
    • 然后创建一个用户对象,并通过反射获取这个对象的信息:
    1. // 创建一个User实例对象
    2. let user = YDWUser()
    3. user.name = "DW"
    4. user.age = 18
    5. user.emails = ["12345@qq.com", "56789@qq.com"]
    6. // 将user对象进行反射
    7. let hMirror = Mirror(reflecting: user)
    8. print("对象类型:\(hMirror.subjectType)")
    9. print("对象子元素个数:\(hMirror.children.count)")
    10. print("--- 对象子元素的属性名和属性值分别如下 ---")
    11. for case let (label?, value) in hMirror.children {
    12. print("属性:\(label) 值:\(value)")
    13. }
    • 打印如下:
    1. 对象类型:YDWUser
    2. 对象子元素个数:4
    3. --- 对象子元素的属性名和属性值分别如下 ---
    4. 属性:name 值:DW
    5. 属性:nickName 值:nil
    6. 属性:age 值:Optional(18)
    7. 属性:emails 值:Optional(["12345@qq.com", "56789@qq.com"])

    5.通过属性名(字符串)获取对应的属性值,并对值做类型判断(包括是否为空)

    • 定义两个方法:getValueByKey() 是用来根据传入的属性名字符串来获取对象中对应的属性值,unwrap() 是用来给可选类型拆包的(对于非可选类型则返回原值):
    1. // 根据属性名字符串获取属性值
    2. func getValueByKey(obj: AnyObject, key: String) -> Any {
    3. let hMirror = Mirror (reflecting: obj)
    4. for case let (label?, value) in hMirror.children {
    5. if label == key {
    6. return unwrap(any: value)
    7. }
    8. }
    9. return NSNull ()
    10. }
    11. // 将可选类型(Optional)拆包
    12. func unwrap(any: Any ) -> Any {
    13. let mi = Mirror (reflecting: any)
    14. if mi.displayStyle! != .optional {
    15. return any
    16. }
    17. if mi.children.count == 0 {
    18. return any
    19. }
    20. let (_, some) = mi.children.first!
    21. return some
    22. }
    • 用上例的 YDWUser 对象做测试:
    1. // 创建一个User实例对象
    2. let user = YDWUser()
    3. user.name = "DW"
    4. user.age = 18
    5. user.emails = ["12345@qq.com", "56789@qq.com"]
    6. // 通过属性名字符串获取对应的值
    7. let name = getValueByKey(obj: user, key: "name")
    8. let nickName = getValueByKey(obj: user, key: "nickName")
    9. let age = getValueByKey(obj: user, key: "age")
    10. let emails = getValueByKey(obj: user, key: "emails")
    11. let tel = getValueByKey(obj: user, key: "tel")
    12. print(name, nickName, age, emails, tel)
    13. // 对于获取到的值进行类型判断
    14. if name is NSNull {
    15. print("name这个属性不存在")
    16. } else if (name as? AnyObject) == nil {
    17. print("name这个属性是个可选类型,且为nil")
    18. } else if name is String {
    19. print("name这个属性String类型,其值为:\(name)")
    20. }
    21. if tel is NSNull {
    22. print("tel这个属性不存在")
    23. } else if (tel as? AnyObject) == nil {
    24. print("tel这个属性是个可选类型,且为nil")
    25. } else if tel is String {
    26. print("tel这个属性String类型,其值为:\(tel)")
    27. }
    • 运行程序,打印如下:

    1. DW nil 18 ["12345@qq.com", "56789@qq.com"] <null>
    2. name这个属性String类型,其值为:DW
    3. tel这个属性不存在

    3.Swift动态性

    • 所谓动态:就是在运行阶段才知道自己的类是什么
    • 父类对象指向子类对象,是动态特性,因为在运行的时候,才知道这个变量真正的类型是子类
    • 具有方法动态派发(函数表派发,消息派发)
    • 继承自OC的类Swift类,具有动态特性
    • dynamic修饰的方法和属性,具有动态特性
    • Swift中的反射机制,是动态特性

    静态派发
    在编译期的时候,编译器就知道要为某个方法调用某种实现

    动态派发
    对于每个方法的调用,编译器必须在方法列表中查找执行方法的实现,比如在运行时判断是选择父类的实现,还是子类的实现。对于对象的内存都是在运行时分配的,因此只能在运行时执行检查

    编译型语言的函数派发方式

    • 直接派发(Direct Dispatch)
      编译后确定了方法的调用地址(静态派发),汇编代码中,直接跳到了方法的地址执行,生成的汇编指令最少,速度最快
      例如C语言,C++默认也是直接派发
      由于缺乏动态性,无法实现多态
    • 函数表派发(Table Dispatch)
      在运行时通过函数查找需要执行的方法,多一次查表的过程,速度比直接派发慢
      C++的虚函数(Virtual Table),维护一个虚函数表,对象创建的时候会保存虚表的指针,调用方之前,会从对象中取出虚表地址,根据编译时的方法偏移量从虚表取出方法的地址,跳到方法的地址执行
    • 消息派发(Message Dispatch)
      objc消息发送机制
    • 性能:直接派发 > 函数表派发 > 消息机制派发

    直接派发

    • 全局函数
    • 使用static声明的方法
    • 使用final声明的所有方法,使用final声明的类里面的所有方法
    • 使用private声明的方法和属性,会隐式final声明
    • 值类型的方法,比如structenum中的方法
    • extension中没有使用@objc修饰的实例方法

    函数表派发

    • 只有引用类型才是函数表派发
    • 在Swift中,类的方法默认使用函数表派发的方式
    • Swift对于协议Protocol默认使用的是函数表派发,协议可以为struct提供多态的支持
    • Swift的函数表叫做 witness table(C++叫做virtual table)
      1.每个子类都有自己的表结构
      2.对于类中每个重写的方法,都有不同的函数指针
      3.当子类添加新方法时,这些方法指针会添加到函数表的末尾
      4.在运行时使用此表来调用函数的实现 (函数表指针 + 函数偏移 就能找到对应的方法)

    消息派发

    • dynamic修饰的方法是消息派发
    • 对于继承自NSObject的Swift类,消息派发方式
      注意: @objc修饰方法,只是把方法暴露给objc,是函数表派发

    例子

    • 普通的实例方法,使用函数表派发
    • @objc声明的方法,使用函数表派发
    • 对于重写了OC的方法。使用消息派发
    • extension中的方法,直接派发
    • dynamic修饰的方法,使用消息派发

    1. //举例说明:
    2. class CustomView: UIView {
    3. /// static修饰:直接派发
    4. static func staticMehod() {}
    5. /// private: 直接派发
    6. private func privateMethod() {}
    7. /// final修饰:直接派发
    8. final func finalMethod() {}
    9. /// 直接派发
    10. static func staticMethod() {}
    11. /// 普通的实例方法 函数表派发
    12. func commonMethod() {}
    13. /// @objc修饰 函数表派发
    14. @objc func method1() {}
    15. /// dynamic修饰: 消息派发
    16. @objc dynamic
    17. func method2() {}
    18. /// 重写了OC的方法: 消息派发
    19. override func layoutSubviews() {
    20. super.layoutSubviews()
    21. }
    22. }
  • 相关阅读:
    PIC单片机4——定时器方波
    5、程序、进程、线程(一)
    函数式编程(纯函数、柯里化、函数组合、函子、接口幂等性)
    OpenGL绘制三角形
    手把手教你如何在Windows11下安装Docker容器
    做期货的阶段(做期货的几个阶段)
    脱壳工具:Youpk的使用详解
    监听DIV元素尺寸变化
    Flume实时采集mysql数据到kafka中并输出
    【卖出备兑看涨期权策略(Covered_call)】
  • 原文地址:https://blog.csdn.net/xiaobo0134/article/details/127902688