Swift Mirror & Error

作者: 正_文 | 来源:发表于2022-08-29 23:18 被阅读0次

    Swift静态语言,他不能像OC一样,直接获取对象的属性和方法,但是Swift标准库依旧提供了反射机制,用来访问成员信息,即Mirror

    一、Mirror反射

    反射:是指可以动态获取类型、成员信息,在运行时可以调用方法、属性等行为的特性。
    使用Mirror的初始化方法reflecting,接着通过.children读取属性名与值。代码:

    class Animal {
        var age: Int = 18
        var name: String = "Cat"
    }
    let mirror = Mirror(reflecting: Animal().self)
    for pro in mirror.children{
        print("\(pro.label ?? ""): \(pro.value)")
    }
    
    //打印结果:
    //age: 18
    //name: Cat
    
    1.1 Mirror源码

    进入Mirror初始化方法,发现传入的类型是Any,则可以直接传t

    // Creates a mirror that reflects on the given instance.
    // - Parameter subject: The instance for which to create a mirror.
    public init(reflecting subject: Any)
    

    查看children

    /// A collection of `Child` elements describing the structure of the reflected subject.
    public let children: Mirror.Children
    👇
    //进入Children,发现是一个AnyCollection,接收一个泛型
    public typealias Children = AnyCollection<Mirror.Child>
    👇
    //进入Child,发现是一个元组类型,由可选的标签和值构成,
    public typealias Child = (label: String?, value: Any)
    

    联系示例代码,通过print("\(pro.label ?? ""): \(pro.value)")打印的就是keyvalue

    1.2 JSON解析

    我们定义了一个Animal类,然后通过一个test方法来解析:

    class Animal {
        var age = 18
        var name = "Cat"
    }
    
    //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 kv: [String: Any] = test(Animal()) as! [String : Any]
    print(kv)
    
    //打印结果:
    //["name": "Cat", "age": 18]
    

    为了方便扩展、通用,我们可以利用封装的思想,将其抽象为一个协议,然后提供一个默认实现,让类遵守协议,进而,model就具备了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 Animal: CustomJSONMap {
        var age = 18
        var name = "Cat"
    }
    
    //使用
    var t = Animal()
    print(t.jsonMap())
    
    //打印结果:
    //当前-18-没有遵守协议
    //当前-Cat-没有遵守协议
    //[:]
    

    没有成功,提示,属性没有遵守协议。添加代码:

    extension Int: CustomJSONMap{}
    extension String: CustomJSONMap{}
    extension Double: CustomJSONMap{}
    
    //打印结果:
    //["age": 18, "name": "Cat"]
    

    HandyJson

    二、Error

    为了JSON处理更规范,更好的维护和管理,我们需要对错误更规范化的处理。Swift,提供了Error协议来标识当前应用程序发生错误的情况。其中Error的定义如下:

    //A type representing an error value that can be thrown.
    public protocol Error { }
    

    如上,Error是一个空协议,其中没有任何实现,这也就意味着你可以遵守该协议,然后自定义错误类型。所以,structClassenum等,都可以遵循这个Error来表示一个错误。
    下面,我们结合JSON解析,做一下错误处理:

    //定义错误类型
    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抛出

    //1、定义一个JSON解析协议
    protocol CustomJSONMap {
        func jsonMap() throws-> Any
    }
    //2、提供一个默认实现
    //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
        }
    }
    

    因为我们使用了throw,所以还需要在递归调用前增加 try 关键字。

    //使用
    var t = Animal()
    print(try t.jsonMap())
    

    三、错误处理方式

    Swift处理错误的方式,主要有两种,try - throwdo - catch

    3.1 try - throw

    使用try关键字,是最简便的,将Error向上抛出,抛给上层函数。但是在使用时,需要注意以下两点:

    1. try?:返回一个可选类型,成功,返回具体的字典值;错误,但并不关心是哪种错误,统一返回nil
    2. try!:表示你对这段代码有绝对的自信,这行代码绝对不会发生错误。
    //CJLTeacher中定义一个height属性,并未遵守协议
    class Animal: CustomJSONMap {
        var age = 18
        var name = "CJL"
        var height = 1.85
    }
    let t = Animal()
    
    //1、try?
    print(try? t.jsonMap())
    //打印结果:nil
    
    //2、try!
    print(try! t.jsonMap())
    //打印结果:
    //SwiftTest/main.swift:256: Fatal error: 'try!' expression unexpectedly raised an error: SwiftTest.JSONMapError.notConformProtocol
    //2022-08-29 23:14:16.107880+0800 SwiftTest[10620:17556994] SwiftTest/main.swift:256: Fatal error: 'try!' expression unexpectedly raised an error: SwiftTest.JSONMapError.notConformProtocol
    
    //3、直接使用try,是向上抛出-
    try t.jsonMap()
    //打印结果:
    //Swift/ErrorType.swift:200: Fatal error: Error raised at top level: SwiftTest.JSONMapError.notConformProtocol
    //2022-08-29 23:14:47.827688+0800 SwiftTest[10648:17557924] Swift/ErrorType.swift:200: Fatal error: Error raised at top level: SwiftTest.JSONMapError.notConformProtocol
    

    从上面可以知道,try错误是向上抛出的,即抛给了上层函数,如果上层函数也不处理,则直接抛给main,main没有办法处理,则直接报错

    3.2 do - catch

    其中do处理正确结果catch处理errorcatch有个隐藏参数,就是error(Error类型)

    do {
        // 处理正常结果
        try t.jsonMap()   // 这里try不会报错了,因为错误都分给了catch
    } catch {
        // 处理错误类型(其中error为Error类型)
        print(error)
    }
    
    //打印结果:notConformProtocol
    
    3.3 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 }
    }
    

    接下来,我们来使用这个协议,继续修改上面JSON的解析代码,新增代码如下:

    //定义更详细的错误信息
    extension JSONMapError: LocalizedError{
        var errorDescription: String?{
            switch self {
            case .emptyKey:
                return "key为空"
            case .notConformProtocol:
                return "没有遵守协议"
            }
        }
    }
    do {
        // 处理正常结果
        try t.jsonMap()   // 这里try不会报错了,因为错误都分给了catch
    } catch {
        // 处理错误类型(其中error为Error类型)
        print(error.localizedDescription)
    }
    
    //打印结果:没有遵守协议
    
    3.3 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解析中的JSONMapError,让其遵守CustomNSError协议,如下:

    //CustomNSError相当于NSError
    extension JSONMapError: CustomNSError{
        var errorCode: Int{
            switch self {
            case .emptyKey:
                return 404
            case .notConformProtocol:
                return 504
            }
        }
    }
    
    do {
        // 处理正常结果
        try t.jsonMap()   // 这里try不会报错了,因为错误都分给了catch
    } catch {
        //1、 处理错误类型(其中error为Error类型)
        print(error.localizedDescription)
    
        //2、处理错误类型(其中error为Error类型
        print((error as? CustomNSError)?.errorCode as Any)
    }
    
    //catch1  打印结果:没有遵守协议
    //catch2  打印结果:Optional(504)
    
    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)
    }
    
    defer(延后处理)
    func functionDefer()  {
        print("begin")
        defer {
            print("defer1")
        }
        defer {
            print("defer2")
        }
        print("end")
    }
    functionDefer()
    
    //打印结果: begin  end  defer2 defer1
    

    函数执行完成之前,才会执行defer代码块内部的逻辑。如果有多个defer代码块,defer代码块的执行顺序是逆序的。

    相关文章

      网友评论

        本文标题:Swift Mirror & Error

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