美文网首页
Swift-进阶 06:反射Mirror & 错误处理

Swift-进阶 06:反射Mirror & 错误处理

作者: 物非0人非 | 来源:发表于2021-09-07 10:00 被阅读0次

    Swift 进阶之路 文章汇总

    反射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)")
    }
    
    
    image

    查看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)
    
    

    这也是为什么能够通过labelvalue打印的原因。即可以在编译时期且不用知道任何类型信息情况下,在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

    image

    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协议

    image

    【修改】:所以在原有基础上增加以下代码

    //Int、String遵守协议
    extension Int: CustomJSONMap{}
    extension String: CustomJSONMap{}
    
    

    修改后的运行结果如下

    image

    错误处理

    为了让我们自己封装的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

      image
    • 所以还需要在方法的返回值箭头前增加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()
    
    

    其运行结果如下

    image

    方式二:do-catch

    通过do-catch来处理JSON解析的错误

    var t = CJLTeacher()
    do{
        try t.jsonMap()
    }catch{
        print(error)
    }
    
    

    运行结果如下

    image

    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)
    }
    
    

    运行结果如下

    image

    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)
    }
    
    

    运行结果如下

    image

    总结

    • Error是swift中错误类型的基本协议,其中LocalizedErrorCustomNSError都遵守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)
    }
    

    相关文章

      网友评论

          本文标题:Swift-进阶 06:反射Mirror & 错误处理

          本文链接:https://www.haomeiwen.com/subject/wzckultx.html