前言
上篇文章Swift 内存管理 & Runtime讲解了Runtime的一个应用场景:Swift类可继承NSObject
,配合使用@objc
修饰符,让OC端的Runtime API
可调用Swift类的方法与属性,同时使用Dynamic
修饰符,也可实现方法交换
,只不过这个交换是在编译期
就确定的。
那么问题来了,既然Swift是一门静态语言
,那到底它有没有类似OC这样的Runtime运行时机制
呢?当然有,就是本篇文章即将介绍的Mirror
反射,虽然没有OC的Runtime
强大,但是也能在运行时
获取对象的类型
和成员变量
,其中第三方库HandyJSON
就是基于Mirror
的机制来实现的。
一、Mirror反射
什么是反射
?👉 可以动态
获取类型
、成员
信息,在运行时
可以调用方法、属性
等行为的特性。
老规矩,还是先上示例代码👇
class LGTeacher {
var age: Int = 18
var name: String = "Luoji"
}
let mirror = Mirror(reflecting: LGTeacher().self)
for pro in mirror.children{
print("\(pro.label ?? ""): \(pro.value)")
}
上述代码是
Mirror
的一个简单的应用场景 👉 通过reflecting
初始化,接着通过.children
读取属性名与值。
1.1 Mirror定义
接下来我们看看Mirror相关文档的API的定义👇
init(reflecting: Any)
传入的类型是Any
。
-
.children
接着查看代码👇
/// A collection of `Child` elements describing the structure of the
/// reflected subject.
public let children: Mirror.Children
继续查看Children
👇
/// The type used to represent substructure.
///
/// When working with a mirror that reflects a bidirectional or random access
/// collection, you may find it useful to "upgrade" instances of this type
/// to `AnyBidirectionalCollection` or `AnyRandomAccessCollection`. For
/// example, to display the last twenty children of a mirror if they can be
/// accessed efficiently, you write the following code:
///
/// if let b = AnyBidirectionalCollection(someMirror.children) {
/// for element in b.suffix(20) {
/// print(element)
/// }
/// }
public typealias Children = AnyCollection<Mirror.Child>
最后看Child
👇
/// An element of the reflected instance's structure.
///
/// When the `label` component in not `nil`, it may represent the name of a
/// stored property or an active `enum` case. If you pass strings to the
/// `descendant(_:_:)` method, labels are used for lookup.
public typealias Child = (label: String?, value: Any)
所以,回到示例代码,通过print("\(pro.label ?? ""): \(pro.value)")
打印的就是key
与value
。
1.2 JSON解析
既然Mirror
可以解析出类的属性key
与value
,那它能做什么? 👉 第一时间联想到的就是JSON解析
。还是先看以下解析的实例代码👇
class LGPerson {
var name = "Luoji"
var age = 18
var student = LGStudent() // 持有对象
}
class LGStudent {
var score = 100.0
}
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) // 递归(model嵌套的场景)
}else{
print("children.label ")
}
}
return keyValue
}
let t = LGPerson()
print(test(t))
代码不难,也是通过reflecting
初始化mirror对象,然后读取其children
属性值,存储到keyValue字典并返回。其中做了判空
,以及递归
处理(处理属性也是model的场景)。
优化一
上述的代码,是在一个swift命名空间中
声明的解析方法
,我们想让其通用化
,基于封装
的思想,自然的想到,将其抽离成一个协议
,这样让每个model遵循该协议,那么model就具备了JSON解析的功能。废话不多说,直接上代码👇
//1、定义一个JSON解析协议
protocol LGJSONMap {
func jsonMap() -> Any
}
//2、在extension中实现jsonMap()解析方法
extension LGJSONMap {
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? LGJSONMap {
if let keyName = children.label {
//递归
keyValue[keyName] = value.jsonMap()
}else{
print("key是nil")
}
}else{
print("当前-\(children.value)-没有遵守协议")
}
}
return keyValue
}
}
//3、让类遵守协议(注意:类中属性的类型也需要遵守协议,否则无法解析)
class LGPerson : LGJSONMap {
var name = "Luoji"
var age = 18
var student = LGStudent() // 持有对象
}
class LGStudent : LGJSONMap {
var score = 100.0
}
//调用
var t = LGPerson()
print(t.jsonMap())
运行👇
上图可知,没有成功,提示属性并没有遵循协议,那么根据错误提示,我们修改代码👇
extension Int: LGJSONMap{}
extension String: LGJSONMap{}
extension Double: LGJSONMap{}
完美解决!这里将解析方法抽离成协议的方法
,是一种封装
的思想。既然提到协议
,那么我们再顺便讲解下Swift中的Protocol协议
的用法吧。
Protocol
Swift的协议
比OC的协议
功能多多了,非常强大,不仅可以声明属性、函数
,支持可选实现
,还可以在extension中
完成属性
和函数
的默认实现
(上述JSON解析就是这样的场景)。
所以,以后大家在开发过程中,如果碰到了类似的这样的场景,也可以封装成协议的方法,很好的隔离代码
,而且让代码看起来非常简洁
。👇
遇到一些
通用功能
的函数,我们可以使用协议封装
起来,只要遵循这个协议,就可以直接调用相应的属性和函数
。
优化二
上述解析方法中,对于错误
的处理方式,只是简单的print
一下,那有没有一种更好的方式,将这些错误封装
成一个对象,并抛出来
给调用方,让调用方自行处理呢?当然有👇
可以通过
系统Error
协议搭配throw
关键字,优雅的抛出错误或返回常规结果,让开发者自己选择处理方式。
二、Error处理
在Swift中,系统提供Error协议
来表示当前应用程序发生错误的情况,并支持使用throw关键字
,优雅的抛出错误。
2.1 Error协议
public protocol Error {
}
上面源码可知 👉 Error是一个空
协议,其中没有任何实现,这也就意味着你可以遵守该协议,然后自定义错误类型
。所以不管是Struct
、Class
、enum
,我们都可以遵循这个Error协议来表示一个错误。我们就先用枚举enum
来标识错误(枚举的功能也十分强大,后续会有专门的一篇文章来讲解
)。
- 先定义错误类型
enum LGJSONMapError: Error{
case emptyKey
case notConformProtocol
}
- 然后调用
extension LGJSONMap{
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? LGJSONMap {
if let keyName = children.label {
//递归
keyValue[keyName] = value.jsonMap()
}else{
return LGJSONMapError.emptyKey
}
}else{
return LGJSONMapError.notConformProtocol
}
}
return keyValue
}
}
但是有个问题,现在return的数据,无法区分是正常的dic还是错误枚举,如何处理呢?👉 可以利用throw
关键字抛出错误
👇
extension LGJSONMap{
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? LGJSONMap {
if let keyName = children.label {
//递归
keyValue[keyName] = value.jsonMap()
}else{
throw LGJSONMapError.emptyKey
}
}else{
throw LGJSONMapError.notConformProtocol
}
}
return keyValue
}
}
将return
改成throw
后,编译器会报错👇
根据错误的提示信息,是因为我们声明的函数没有声明throw
,那么我们修改函数声明👇
protocol LGJSONMap {
func jsonMap() throws -> Any
}
仍旧会报错,用了throw没有用try,那么补齐👇
我们把属性遵循协议的注释掉,运行看看是否会抛出错误👇
果然抛出了LGJSONMapError.notConformProtocol
错误。
Error处理的方式
Swift中错误处理的方式主要有以下两种:
- 使用
try - throw
最简便的,即甩锅,将这个抛出给别人(向上抛出,抛给上层调用方函数)。
但是需要注意以下两点:-
try?
👉 返回一个可选类型
,只有两种结果:要么成功,返回具体的值
;要么错误,但并不关心是哪种错误,统一返回nil
-
print(try? t.jsonMap())
-
try!
👉 表示你对这段代码有绝对的自信,这行代码绝对不会发生错误
print(try! t.jsonMap())
从上面可以知道,错误是向上抛出
的,即抛给了调用函数,如果调用的上层函数也不处理,则直接抛给main
,main
没有办法处理,则直接报错
。
- 使用
do - catch
其中do
处理正确结果
,catch
处理error
,catch有个隐藏参数
,就是error
(Error类型)
do {
// 处理正常结果
try t.jsonMap() // 这里try不会报错了,因为错误都分给了catch
} catch {
// 处理错误类型(其中error为Error类型)
print(error)
}
LocalizedError
上述的错误处理中,只知道了错误类型
,如果还想知道错误相关的描述
或其它信息
,则需要使用LocalizedError
,定义👇
/// Describes an error that provides localized messages describing why
/// an error occurred and provides more information about the error.
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 }
}
接下来,我们来使用一下这个协议,修改上面的解析代码👇
//定义错误类型
enum LGJSONMapError: Error{
case emptyKey
case notConformProtocol
}
extension LGJSONMapError : LocalizedError {
var errorDescription: String?{
switch self {
case .emptyKey:
return "key为空"
case .notConformProtocol:
return "没有遵守协议"
}
}
}
//调用
var t = LGPerson()
do {
// 处理正常结果
try t.jsonMap() // 这里try不会报错了,因为错误都分给了catch
} catch {
// 处理错误类型(其中error为Error类型)
print(error.localizedDescription)
}
完美,错误描述被打印了出来!
CustomNSError协议
还有一个CustomNSError协议
,相当于OC中的NSError
,其定义如下,有三个默认属性👇
/// Describes an error type that specifically provides a domain, code,
/// and user-info dictionary.
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解析中的LGJSONMapError,让其遵守CustomNSError协议👇
extension LGJSONMapError: CustomNSError{
var errorCode: Int{
switch self {
case .emptyKey:
return 404
case .notConformProtocol:
return 504
}
}
}
//调用
var t = LGPerson()
do {
// 处理正常结果
try t.jsonMap() // 这里try不会报错了,因为错误都分给了catch
} catch {
// 处理错误类型(其中error为Error类型)
// print(error.localizedDescription)
print((error as? LGJSONMapError)?.errorCode)
}
rethorws
rethrows是处理这种场景的错误 👉 函数1
是函数2
的入参
,其中函数1
中有throws错误
。
上代码
func add(_ a: Int, _ b: Int) throws -> Int {
return a + b
}
func exec(_ f:(Int, Int) throws -> Int, _ a: Int, _ b: Int) rethrows {
print(try f(a, b) )
}
do {
try exec(add, 1, 2)
}catch {
print(error.localizedDescription)
}
rethrows将函数参数add的错误再次抛出,统一交由外部do - catch处理。
defer(延后处理)
func functionDefer() {
print("begin")
defer {
print("defer")
}
print("end")
}
函数执行完成之前
才会执行defer代码块内部的逻辑。如果有多个defer代码块,执行顺序怎么样?
func functionDefer() {
print("begin")
defer {
print("defer1")
}
defer {
print("defer2")
}
print("end")
}
defer代码块的执行顺序是逆序
的。
assert(断言)
很多编程语言都有断言机制
,不符合指定条件
就抛出运行时错误
,常用于调试(Debug)阶段
的条件判断
。例如👇
func devide(_ a: Int, _ b: Int) -> Int {
assert(b != 0, "除数不能为0")
return a / b
}
默认情况下,Swift断言只会在debug模式下生效,release模式下忽略👇
总结
本篇文章主要利用Swift类中的Mirror
反射机制,封装了一套JSON解析协议
,同时将解析时错误也进行了封装处理,顺便讲解了Error
的几个常用的协议。