Swift之深入解析反射Mirror与错误处理

Posted Forever_wj

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Swift之深入解析反射Mirror与错误处理相关的知识,希望对你有一定的参考价值。

一、反射 Mirror 简介

  • 反射是指可以动态获取类型、成员信息,同时在运行时(而非编译时)可以动态调用任意方法、属性等行为的特性。
  • 在使用 OC 开发时很少强调其反射概念,因为 OC 的 runtime 要比其他语言中的反射强大的多,在 OC 中可以很简单的实现字符串和类型的转换(NSClassFromString()),实现动态方法调用(performSelector: withObject:),动态赋值(KVC)等等。
  • 对于纯 Swift 类来说,并不支持像 OC runtime 那样操作,但是 Swift 标准库仍然提供了反射机制访问成员信息,即 Mirror。
  • Swift 的反射机制是基于一个叫 Mirror 的 struct 来实现的,其内部有如下属性和方法:
	/// 属性 Mirror.Children (label: String?, value: Any)
	let children: Mirror.Children
	/// 自定义反射
	var customMirror: Mirror
	/// 反射描述 一般为 Mirror for 类型
	var description: String
	/// 显示类型,基本类型为nil 枚举值: class, enum , struce, dictionary, array, set, tuple
	let displayStyle: Mirror.DisplayStyle?
	/// 类型
	let subjectType: Any.Type
	/// 父类反射, 没有父类为nil
	var superclassMirror: Mirror?
  • 进入 children:
	public let children: Mirror.Children

	// 进入Children,发现是一个AnyCollection,接收一个泛型
	public typealias Children = AnyCollection<Mirror.Child>

	// 进入Child,发现是一个元组类型,由可选的标签和值构成,
	public typealias Child = (label: String?, value: Any)
  • 这就是为什么可以通过 label、value 打印的原因,即可以在编译时期且不用知道任何类型信息情况下,在 Child 的值上用 Mirror 去遍历整个对象的层级视图。

二、反射 Mirror 使用

① Mirror 转换对象为字典
  • 定义如下对象:
	struct Person {
	    var name: String = "YDW"
	    var isMale: Bool = true
	    var birthday: Date = Date()
	}
	 
	class Animal: NSObject {
	    private var eat: String = "吃"
	    var age: Int = 0
	    var optionValue: String?
	}
	 
	class Cat: Animal {
	    var like: [String] = ["mouse", "fish"]
	    var master = Person()
	}
  • 遍历出字典:
	func mapDic(mirror: Mirror) -> [String: Any] {
	    var dic: [String: Any] = [:]
	    for child in mirror.children {
	        // 如果没有labe就会被抛弃
	        if let label = child.label {
	            let propertyMirror = Mirror(reflecting: child.value)
	               dic[label] = child.value
	        }
	    }
	    // 添加父类属性
	    if let superMirror = mirror.superclassMirror {
	        let superDic = mapDic(mirror: superMirror)
	        for p in superDic {
	            dic[p.key] = p.value
	        }
	    }
	    return dic
	}
  • 使用 Mirror 转换对象并打印结果, 可以看到可以打印出私有属性:
	// Mirror使用
	let objc  = Cat()
	let  mirror = Mirror(reflecting: objc)
	let mirrorDic = mapDic(mirror: mirror)
	print(mirrorDic)
	 
	// 打印结果
	["like": ["mouse", "fish"], "optionValue": nil, "eat": "吃", "age": 0, "master": __lldb_expr_48.Person(name: "YDW", isMale: true, birthday: 2021-06-10 11:24:30 +0000)]
  • 在实际运用中,可以将应用于元组参数传递(比如网路请求传参,传入元组,网络请求时转换为字典),优点:外部使用知道具体传入什么参数,参数更改不影响方法错误。
	// 外部参数定义
	var params = (title: "name", comment: "Mirror")
	 
	// 网络层统一转换为字典,进行网路请求
	let paramsDic = mapDic(mirror: Mirror(reflecting: params))
	print(paramsDic)
	 
	// 打印结果
	["title": "name", "comment": "Mirror"]
  • 需要注意是只能传入基本类型,并且元组参数要命名,如果直接使用(“name”,“Mirror”)则会变成下面这种情况:
	// 外部参数定义
	var params = ("name","Mirror")
	 
	// 网络层统一转换为字典,进行网路请求
	let paramsDic = mapDic(mirror: Mirror(reflecting: params))
	print(paramsDic)
	// 打印
	[".1": "Mirror", ".0": "name"]
② JSON 解析
  • 定义一个 YDWTeacher 类:
	class YDWTeacher {
	    var age = 18
	    var name = "YDW"
	}
  • 运用 Mirror 来解析,实现代码如下:
	// 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 = YDWTeacher()
	print(test(t))
  • 运行代码,打印结果如下:
	["name": "YDW","age": 18]
  • 如果想在工程中大量的使用上述的 JSON 解析,可以将 Mirror JSON 解析抽取成一个协议,然后提供一个默认实现,让类遵守协议,如下:
	// 定义JSON解析协议
	protocol CustomJSONMap {
	    func jsonMap() -> Any
	}
	
	// 提供默认实现
	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
	    }
	}
	
	// 让类遵守协议(注意:类中属性的类型也需要遵守协议,否则无法解析)
	class YDWTeacher: CustomJSONMap {
	    var age = 18
	    var name = "YDW"
	}
	
	// 使用
	var t = YDWTeacher()
	print(t.jsonMap())
  • 运行,可以发现并没达到我们预想的结果,这是因为 YDWTeacher 的属性的类型也需要遵守 CustomJSONMap 协议:
	当前-18-没有遵守协议
	当前-YDW-没有遵守协议
  • 修改如下,这样就可以达到我们预想的结果:
	// Int、String遵守协议
	extension Int: CustomJSONMap{}
	extension Sring: CustomJSONMap{}

	// 打印结果 
	["name": "YDW", "age": 18]
③ 错误处理
  • 为了让封装的JSON 解析更加完善,除了对正常返回的处理,还需要对其中的错误进行处理,在上面的封装中,采用的是 print 打印的,这样并不规范,也不好维护及管理。那么如何在 swift 中正确的表达错误呢?
  • Swift 中,提供了 Error 协议来标识当前应用程序发生错误的情况,其中 Error 的定义如下:
	public protocol Error {
	}
  • 可以看到:Error 是一个空协议,其中没有任何实现,这也就意味着可以遵守该协议,然后自定义错误类型,因此不管是 struct、Class、enum,都可以遵循这个 Error 来表示一个错误。
  • 那么上面封装的 JSON 解析修改错误处理,首先定义一个 JSONMapError 错误枚举,将默认实现的 print 替换成枚举类型:
	// 定义错误类型
	enum JSONMapError: Error{
	    case emptyKey
	    case notConformProtocol
	}
	
	// 定义JSON解析协议
	protocol CustomJSONMap {
	    func jsonMap() -> Any
	}
	// 提供默认实现
	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 关键字(即将原本 return 的错误,改成 throw 抛出):
	// 提供默认实现
	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 抛出错误后,编译器提示有错,其原因是方法并没有声明成 throw:

在这里插入图片描述

  • 因此还需要在方法的返回值箭头前增加 throws(表示将方法中错误抛出),修改后的默认实现代码如下所示:
	// 提供默认实现
	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 关键字:
	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
	    }
	}
  • 封装完成,使用如下:
	// 使用时需要加上try
	var t = YDWTeacher()
	print(try t.jsonMap())
④ 获取类型,属性个数及其值
  • 定义一个用户类:
	class YDWUser {
	     var name: String = ""
	     var nickName: String?
	     var age: Int?
	     var emails: [String]?
	}
  • 然后创建一个用户对象,并通过反射获取这个对象的信息:
	// 创建一个User实例对象
	let user = YDWUser()
	user.name = "DW"
	user.age = 18
	user.emails = ["12345@qq.com", "56789@qq.com"]
                 
	// 将user对象进行反射
	let hMirror = Mirror(reflecting: user)
                 
	print("对象类型:\\(hMirror.subjectType)")
	print("对象子元素个数:\\(hMirror.children.count)")
                 
	print("--- 对象子元素的属性名和属性值分别如下 ---")
	for case let  (label?, value)  in  hMirror.children {
		print("属性:\\(label)     值:\\(value)")
	}
  • 运行程序,打印如下:
	对象类型:YDWUser
	对象子元素个数:4
	--- 对象子元素的属性名和属性值分别如下 ---
	属性:name     值:DW
	属性:nickName     值:nil
	属性:age     值:Optional(18)
	属性:emails     值:Optional(["12345@qq.com", "56789@qq.com"])
⑤ 通过属性名(字符串)获取对应的属性值,并对值做类型判断(包括是否为空)
  • 定义两个方法:getValueByKey() 是用来根据传入的属性名字符串来获取对象中对应的属性值,unwrap() 是用来给可选类型拆包的(对于非可选类型则返回原值):
	// 根据属性名字符串获取属性值
    func getValueByKey(obj: AnyObject, key: String) -> Any {
         let hMirror = Mirror (reflecting: obj)
         for case let (label?, value) in hMirror.children {
             if label == key {
                return unwrap(any: value)
             }
         }
         return  NSNull ()
    }
     
    // 将可选类型(Optional)拆包
    func unwrap(any: Any ) ->  Any  {
         let mi =  Mirror (reflecting: any)
        if mi.displayStyle! != .optional {
             return any
         }
         
         if mi.children.count == 0 {
            return any
         }
         let  (_, some) = mi.children.first!
         return  some
    }
  • 用上例的 YDWUser 对象做测试:
	// 创建一个User实例对象
    let user = YDWUser()
    user.name = "DW"
    user.age = 18
    user.emails = ["12345@qq.com", "56789@qq.com"]
     
    // 通过属性名字符串获取对应的值
    let name = getValueByKey(obj: user, key:  "name")
    let nickName = getValueByKey(obj: user, key:  "nickName")
    let age = getValueByKey(obj: user, key:  "age")
    let emails = getValueByKey(obj: user, key:  "emails")
    let tel = getValueByKey(obj: user, key:  "tel")
    print(name, nickName, age, emails, tel)
     
    // 对于获取到的值进行类型判断
    if name is NSNull {
        print("name这个属性不存在")
    } else if (name as? AnyObject) == nil {
        print("name这个属性是个可选类型,且为nil")
    } else if name is String {
        print("name这个属性String类型,其值为:\\(name)")
    }
    if nickName is NSNull {
        print("nickName这个属性不存在")
    } else if (nickName as? AnyObject) == nil {
        print("nickName这个属性是个可选类型,且为nil")
    } else if nickName is String {
        print("nickName这个属性String类型,其值为:\\(nickName)")
    }
    if tel is NSNull {
        print("tel这个属性不存在")
    } else if (tel as? AnyObject) == nil {
        print("tel这个属性是个可选类型,且为nil")
    } else if tel is String {
        print("tel这个属性String类型,其值为:\\(tel)")
    }
  • 运行程序,打印如下:
	DW nil 18 ["12345@qq.com", "56789@qq.com"] <null>
	name这个属性String类型,其值为:DW
	tel这个属性不存在

三、Swift 错误处理

① 关键字 try
  • 简单便捷,将错误向上层函数抛出,使用时,需要注意:
    • try? 返回一个可选类型,只有两种结果:成功即返回具体的字典值,错误,不管是哪种错误,统一返回 nil;
    • try! 代码绝对不会发生错误。
  • 通过 try 来处理 JSON 解析的错误:
	// YDWTeacher中定义一个height属性,并未遵守协议
	class YDWTeacher: CustomJSONMap {
	    var age = 18
	    var name = "YDW"
	    var height = 1.85
	}
	
	// try? 示例
	var t = YDWTeacher()
	print(try? t.jsonMap())
	
	// 打印结果
	nil
	
	// try! 示例
	var t = YDWTeacher()
	print(try! t.jsonMap())
	
	// 打印结果
	Fatal error: 'try!' expression unexpectedly raised an error: _5_MirrorAndError.JSONMapError.notConformProtocol: file _5_MirrorAndError/main.swift, line 90
	
	// 如果直接使用try,是向上层函数抛出
	var t = YDWTeacher()
	try t.jsonMap()
	
	// 打印结果
	Fatal error: Error raised at top level: _5_MirrorAndError.JSONMapError.notConformProtocol: file Swift/ErrorType.swift, line 200
  • 从上面可以知道,错误是向上层函数抛出,如果上层函数也不处理,则直接抛给 main,那么 main 没有办法处理,则直接报错。
② do…catch
  • 通过 do-catch 来处理 JSON 解析的错误:
	var t = YDWTeacher()
	do {
	    try t.jsonMap()
	} catch {
	    print(error)
	}
③ 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 recoverySuggestioniOS开发-Swift进阶之反射Mirror & 错误处理!

iOS开发-Swift进阶之Mirror源码解析!

Swift-进阶 07:Mirror源码解析

Swift之深入解析异步函数async/await的使用与运行机制

反射 Mirror | Swift 动态性

反射 Mirror | Swift 动态性