「这是我参与2022首次更文挑战的第11天,活动详情查看:2022首次更文挑战」。
- 本文主要介绍swift中
Mirror反射
1. Mirror的介绍
mirror主要是反射就是指可以动态的获取类型,成员信息,在运行时可以调用方法,属性等行为特性。使用oc中较少强调反射的概念,因为oc中的runtime要比其他语言中的反射强大。swift是一门安全的语言,无法像oc直接操作类的属性和方法等。但是swift的标准库仍然提供了反射机制让我们访问成员信息。Swift的反射机制是基于一个叫Mirror的结构体进行实现的,通过创建这个对象,查询实例。
- 看个例子
class Person {
var age = 10
}
let mirror = Mirror(reflecting: Person.self)
for pro in mirror.children{
print("\(String(describing: pro.label)):\(pro.value)")
}
打印没有结果,点击查看初始化的方法,需要我们传一个实例化的对象。
实例对象后打印出child了
我们查看下children,是一个集合类型里面是范型参数child,child是一个元祖类型存储了对象的属性名字和值。
typealias的使用 这里简单说明下typealias的使用,假设我们有一个方法,传入的参数不一致,但是当前的result回调的结果是一样的。我们这样写会比较麻烦
func loadData(reslut:(_ data:Data,_ failure:String)->Void) {
}
使用的地方比较多的话,我们可以定义一个闭包,使用的时候可以当作尾随闭包
typealias completionBlock = (_ data:Data,_ failure:String)->()
func loadData(result:completionBlock) {
}
上面我们打印了当前对象的属性名称和对应的值,我们还可以打印:
- public let
subjectType: Any.Type 反射对象的类型 - public let
children: Mirror.Children 存放当前属性名字和属性值的集合 - public let
displayStyle: Mirror.DisplayStyle? 反射的类型
2. 使用Mirror进行解析
先看一个案例,我们通常递归的方式把属性和属性值存到字典中,使用guard 进行条件检测:比如当前子属性为空,就直接返回自身,避免了if的逻辑判断。
func testMirror(_ mirrorObject:Any) -> Any {
let mirror = Mirror(reflecting: mirrorObject)
guard !mirror.children.isEmpty else {
return mirrorObject//判断是否存在属性,不存在直接返回对象
}
var reslut:[String:Any] = [:]
for child in mirror.children {
if let key = child.label {
reslut[key] = testMirror(child.value)//进行递归,比如一些实例对象的属性,直到当前属性已经没有子属性了
}else{
print("key is empty")
}
}
return reslut
}
对于实例对象,这个方法没问题,但是我们想要对于结构体,枚举等都具备这个属性。我们使用一个协议来定义这个方法,并实现它,后面只要我们的类或者结构体遵循这个协议就可以调用
protocol KBJsonMap {
func jsonMap()->Any
}
extension KBJsonMap{
func jsonMap() -> Any {
let mirror = Mirror(reflecting: self)
guard !mirror.children.isEmpty else {
return self//判断是否存在属性,不存在直接返回对象
}
var reslut:[String:Any] = [:]
for child in mirror.children {
if let value = child.value as? KBJsonMap {
//判断当前值是否遵循这个协议,否则无法递归
if let key = child.label {
reslut[key] = value.jsonMap()
//进行递归,比如一些实例对象的属性,直到当前属性已经没有子属性了
}else{
print("key is empty")
}
}else{
print("当前值:\(child.value) 没有遵循协议")
}
}
return reslut
}
}
- 使用
我们在使用的时候属性
必须遵循这个协议,否则无法调用该方法。
- 遵循协议
3. 错误处理
我们在上面的案例中使用了很多print打印错误信息,我们可以使用swift中的Error协议来标示当前程序发生错误的情况。
是一个空的协议,其中没有任何实现,这也就意味着你可以遵守该协议,然后自定义错误类型。所以不管是我们的struct、Class、enum,我们都可以遵循这个Error来表示一个错误
我们根据上面的例子,定义一个枚举JsonMapError遵循这个协议
enum JSONMapError: Error{
case emptyKey
case notConformProtocol
}
替换我们上面的的打印
虽然返回了枚举的类型,但是无法知道到底发生了什么错误。我们应该使用throw关键字抛出错误。
但是编译器告诉我们的方法没有声明throws,因此我们方法定义成可以throw
编译还是报错,我们要在协议定义的时候也加上throws,但是这个时候有一个问题,就是value默认也会调用jsonMap,也会有错误抛出。
我们根据编译器提示修改代码,添加try
3.1 swift中错误处理方式:try
try关键字是最简便的,主要的作用就是:甩锅
我们在当前类中的Float类型不遵循协议:
- 我们使用
try?可选值,返回的值要么成功,具体的值,要么失败,统一返回nil
- 我们使用
try!强制解包,表示这段代码绝对没有问题,不会报错,否则就是崩溃
- 我们使用
try错误是向上抛出的,即抛给了上层函数
所以在处理错误中,我们不想处理当前的错误,可以通过这样的方式向上传递错误,但是最终调用者必须要处理当前错误,否则的话,程序还是发生异常。
3.2 swift中处理错误方式:do...catch
通过打印catch捕获当前错误,打印我们返回的错误。但是有的时候我们想要更详细的错误,可以通过下面的方式
3.3 LocalizedError 协议
我们查看LocalizedError的定义,里面包含了错误描述,失败原因,建议,帮助等。
我们让我们之前的JsonMapError遵守这个协议,打印更加详细的描述。
enum JSONMapError: Error,LocalizedError{
case emptyKey
case notConformProtocol
var errorDescription: String?{
switch self {
case .emptyKey:
return "key是空的"
case .notConformProtocol:
return "没有遵守协议"
}
}
}
使用
3.4 CustomNSError协议
相当于OC中的NSError,其定义如下,有三个默认属性
修改
extension JSONMapError:CustomNSError
{
var errorCode: Int{
switch self {
case .emptyKey:
return 404
case .notConformProtocol:
return 400
}
}
}
- 使用
4. 总结
- 对于swift是
静态语言无法使用runtime,我们想要获取其属性和值的时候可以使用mirror映射。我们可以通过协议的方法,让类或者属性递归查找其子属性,在递归的过程中需要属性值遵循我们解析的协议。 - 对于我们错误可以使用
thorw抛出,错误处理可以使用try或者do...catch。当我们不确定是否有错误的话使用try?(可选值,没有值的话返回nil),try!则表示对流程的肯定,肯定没有错否则程序会崩溃;try则向上抛出错误,抛给了上层函数,如果最上层没有处理也会崩溃。do...catch配合try使用,通过cathc捕获错误,进行处理。 - 对于错误
error是一个空的协议,我们可以自定义协议。当我们自定义的协议比较简单,想要详细描述的话可以遵守LocalizedError、CustomNSError,他们都遵守Error。