本文主要介绍Mirror
的使用以及使用Mirror进行JSON解析的错误处理
反射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)") }
查看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)
这也是为什么能够通过label
、value
打印的原因。即可以在编译时期且不用知道任何类型信息情况下,在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
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
协议
【修改】:所以在原有基础上增加以下代码
//Int、String遵守协议 extension Int: CustomJSONMap{} extension String: CustomJSONMap{}
修改后的运行结果如下
错误处理
为了让我们自己封装的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
- 所以还需要在方法的返回值箭头前增加
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 } } <!--使用时需要加上try--> 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 <!--3、如果直接使用try,是向上抛出--> 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()
其运行结果如下
方式二:do-catch
通过do-catch
来处理JSON解析的错误
var t = CJLTeacher() 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 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) }
运行结果如下
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) }
运行结果如下
总结
Error
是swift中错误类型的基本协议,其中LocalizedError
、CustomNSError
都遵守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) }