swfit进阶-10-swift中的Mirror

1,104 阅读6分钟

「这是我参与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)")

    

}

打印没有结果,点击查看初始化的方法,需要我们传一个实例化的对象。

image.png

实例对象后打印出child

image.png

我们查看下children,是一个集合类型里面是范型参数childchild是一个元祖类型存储了对象的属性名字和值。

image.png

  • 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? 反射的类型

image.png

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
}

image.png

对于实例对象,这个方法没问题,但是我们想要对于结构体枚举等都具备这个属性。我们使用一个协议来定义这个方法,并实现它,后面只要我们的类或者结构体遵循这个协议就可以调用

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

        

    }

}

  • 使用 我们在使用的时候属性必须遵循这个协议,否则无法调用该方法。

image.png

  • 遵循协议

image.png

3. 错误处理

我们在上面的案例中使用了很多print打印错误信息,我们可以使用swift中的Error协议来标示当前程序发生错误的情况。

image.png

是一个空的协议,其中没有任何实现,这也就意味着你可以遵守该协议,然后自定义错误类型。所以不管是我们的struct、Class、enum,我们都可以遵循这个Error来表示一个错误

我们根据上面的例子,定义一个枚举JsonMapError遵循这个协议

enum JSONMapError: Error{

    case emptyKey

    case notConformProtocol

}

替换我们上面的的打印

image.png

虽然返回了枚举的类型,但是无法知道到底发生了什么错误。我们应该使用throw关键字抛出错误。

image.png

但是编译器告诉我们的方法没有声明throws,因此我们方法定义成可以throw

image.png

编译还是报错,我们要在协议定义的时候也加上throws,但是这个时候有一个问题,就是value默认也会调用jsonMap,也会有错误抛出。

image.png

我们根据编译器提示修改代码,添加try

image.png

3.1 swift中错误处理方式:try

try关键字是最简便的,主要的作用就是:甩锅 我们在当前类中的Float类型不遵循协议:

  • 我们使用try? 可选值,返回的值要么成功,具体的值,要么失败,统一返回nil

image.png

  • 我们使用try!强制解包,表示这段代码绝对没有问题,不会报错,否则就是崩溃

image.png

  • 我们使用try 错误是向上抛出的,即抛给了上层函数

image.png

所以在处理错误中,我们不想处理当前的错误,可以通过这样的方式向上传递错误,但是最终调用者必须要处理当前错误,否则的话,程序还是发生异常

3.2 swift中处理错误方式:do...catch

image.png

通过打印catch捕获当前错误,打印我们返回的错误。但是有的时候我们想要更详细的错误,可以通过下面的方式

3.3 LocalizedError 协议

我们查看LocalizedError的定义,里面包含了错误描述,失败原因,建议,帮助等。 image.png

我们让我们之前的JsonMapError遵守这个协议,打印更加详细的描述。


enum JSONMapError: Error,LocalizedError{

    case emptyKey

    case notConformProtocol

    var errorDescription: String?{

           switch self {

           case .emptyKey:

               return "key是空的"

           case .notConformProtocol:

               return "没有遵守协议"

           }

       }

}

使用

image.png

3.4 CustomNSError协议

相当于OC中的NSError,其定义如下,有三个默认属性

image.png

修改

extension JSONMapError:CustomNSError

{

    var errorCode: Int{

        switch self {

        case .emptyKey:

            return 404

        case .notConformProtocol:

        return 400

       

        }

        

    }

}
  • 使用

image.png

4. 总结

  1. 对于swift是静态语言无法使用runtime,我们想要获取其属性和值的时候可以使用mirror映射。我们可以通过协议的方法,让类或者属性递归查找其子属性,在递归的过程中需要属性值遵循我们解析的协议
  2. 对于我们错误可以使用thorw抛出,错误处理可以使用try或者do...catch。当我们不确定是否有错误的话使用try?(可选值,没有值的话返回nil),try!则表示对流程的肯定,肯定没有错否则程序会崩溃;try向上抛出错误,抛给了上层函数,如果最上层没有处理也会崩溃do...catch配合try使用,通过cathc捕获错误,进行处理。
  3. 对于错误error是一个空的协议,我们可以自定义协议。当我们自定义的协议比较简单,想要详细描述的话可以遵守LocalizedErrorCustomNSError,他们都遵守Error