美文网首页swift进阶
swift进阶九:Mirror反射、Protocol、Error

swift进阶九:Mirror反射、Protocol、Error

作者: markhetao | 来源:发表于2020-12-18 00:03 被阅读0次

    swift进阶 学习大纲

    上一节,我们分析了闭包 & Runtime & Any等类型

    介绍Runtime运行时时,我们知道swift静态语言,但可兼容OC类实现objc_msgSend消息发送机制。

    • swiftdynamic声明的函数可在extension中进行函数替换。不过这个替换编译期完成了。

    那,swift有没有自己的运行时机制呢?

    • 有,Mirror反射机制。虽然OC运行时那么强大。但支持运行时获取对象类型成员变量HandyJSON就是基于Mirror思想,直接从内存读取来实现的😃)

    本节,我们先体验一下Mirror,下一节,我们深入研究Mirror底层原理

    1. Mirror体验
    2. protocol协议
    3. Error错误机制(try、throws、rethrows)
    4. defer
    5. assert

    1. Mirror体验

    • Mirror(反射),可以动态获取类型成员变量,在运行时可以调用方法属性等行为的特性
      对于一个纯swift类来说,并不支持我们直接像OCRuntime那样操作。但Swift标准库给我们提供简单实用反射机制
    • 测试案例:
    class HTPerson {
       var name = "ht"
       var age = 18
    }
    
    let p = HTPerson()
    let mirror = Mirror(reflecting: p.self)
    
    for pro in mirror.children {
       print("\(pro.label ?? ""):\(pro.value)")
    }
    
    • 打印结果:
    image.png
    • 进入Mirror内部可以看到:
    1. Mirror是一个struct结构体

      image.png
    2. Mirror 初始化时,入参为Any类型

      image.png
    3. children属性为(label: String?, value: Any)元组类型

      image.png
    • Mirror用途很多,最常用的是用于JSON解析
    class HTPerson {
        var name = "ht"
        var age = 18
        var student = HTStudent() // 持有对象
    }
    
    class HTStudent {
        var double = 10.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)  // 递归(继续遍历person中的对象属性)
            }else{
                print("children.label ")
            }
        }
        return keyValue
    }
        
    let t = HTPerson()
    print(test(t))
    
    • 打印结果:


      image.png

    分析:

    1. mirror成功读取对象属性名属性值,可使用字典存储和输出;

    2. 每个属性都会默认当作对象来处理,通过mirror.children可判断当前对象是否拥有属性,没有就跳出当前属性递归流程。 这样可保证所有读取children完整层级信息

    (ps:递归实际是利用特性,直接间接 调用自身实现层级读取需求,递归需要有明确的终止条件)

    • 上面代码有个不好的点: 错误结果print打印,外界不知道
      swift中,错误信息一般使用Error进行表示。

    2. Protocol协议

    • 在介绍Error之前,我们先介绍一下Swift协议
      Swift协议非常强大,不仅可以声明属性函数,支持可选实现,还可以在extension中完成属性函数默认实现

    • 以上面案例为例:

    如果每次读取属性,我们都需要调用test函数,会显得非常麻烦

    • 协议可以帮我们默认实现这个方法,只要对象遵守这个协议,都可以直接使用

    【思路】

    1. 创建一个协议(CustomJSONMap),声明jsonMap函数,并在extension中默认实现jsonMap
    2. 创建一个枚举JSONMapError,对所有错误类型进行列举
    3. jsonMap内部支持Mirror所有遵循CustomJSONMap协议的属性。对未遵循协议的属性和属性名为空的属性返回相应的错误类型
    // 错误枚举
    enum JSONMapError {
        case emptyError         // 空
        case notConformProtocol // 未遵守协议
    }
    
    // 协议(自带jsonMap函数)
    protocol CustomJSONMap {
        func jsonMap() ->Any
    }
    
    // extension中默认jsonMap实现
    extension CustomJSONMap {
        
        func jsonMap() -> Any {
    
            let mirror = Mirror(reflecting: self)
            
            guard !mirror.children.isEmpty else { return self }
            
            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.emptyError      // 属性名为空
                    }
                    
                } else {
                    return JSONMapError.notConformProtocol  // children未遵守CustomJSONMap协议
                }
            }
            return keyValue
        }
    }
    
    class HTPerson: CustomJSONMap {
        var name = "ht"
        var age = 18
    }
    
    let t = HTPerson()
    print(t.jsonMap())
    
    • 运行上述案例,可以看到打印了notConformProtocol
      因为nameage属性分别是StringInt类型,他们没有遵守CustomJSONMap协议。

      image.png
    • 既然如此,我们让StringInt都遵守CustomJSONMap协议,再运行。

    extension String: CustomJSONMap {}
    extension Int: CustomJSONMap {}
    
    image.png
    • 完美打印所有内容

    在我们的开发中,protocol协议一大利器,使用非常方便。

    • 遇到一些通用函数,我们可以使用协议包装起来,只要遵循这个协议,就可以直接调用相应的属性函数。很好的隔离代码,而且让代码看起来非常简洁
    • 上面的错误信息,我们只是使用枚举统一罗列return时常规结果错误类型混在一起返回,让我们很不舒服

    如何解决?

    • 可以通过系统Error协议搭配throw关键字,优雅的抛出错误或返回常规结果,让开发者自己选择处理方式。

    3. Error机制(try、throws、rethrows)

    swift中,系统提供Error协议来表示当前应用程序发生错误的情况,并支持使用throw关键字,优雅抛出错误

    3.1 基础Error协议

    public protocol Error { }
    
    • 系统的Error协议,就是一个空的公共协议, 不管是classstruct还是enum,都可以通过遵守这个协议,来表达错误

    enum为例:

    // 错误枚举,遵循Error
    enum JSONMapError: Error {
        case emptyError         // 空
        case notConformProtocol // 未遵守协议
    }
    
    // 协议(自带jsonMap函数)
    protocol CustomJSONMap {
        func jsonMap() throws ->Any
    }
    
    // extension中默认jsonMap实现
    extension CustomJSONMap {
        
        func jsonMap() throws -> Any {
            let mirror = Mirror(reflecting: self)
            
            guard !mirror.children.isEmpty else { return self }
            
            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() // 加了throws后,需要使用try执行 jsonmap()
                    } else {
                        throw JSONMapError.emptyError           // 属性名为空
                    }
                } else {
                    throw JSONMapError.notConformProtocol       // children未遵守CustomJSONMap协议
                }
            }
            return keyValue
        }
    }
    
    class HTPerson: CustomJSONMap {
        var name = "ht"     // String未继承CustomJSONMap,会error
        var age = 18        // Int未继承CustomJSONMap,会error
    }
    
    let t = HTPerson()
    
    // do...catch 分开处理,catch中有默认参数error。
    do {
        // 处理正常结果
        print(try t.jsonMap())
    } catch {
        // 处理错误类型(其中error为Error类型)
        print(error)
    }
    
    //print(try t.jsonMap())    // try : 向上甩锅,将错误抛给上层函数处理(上层没处理,就crash)
    print(try? t.jsonMap())   // try?: 只关注正常结果,忽略错误(如果错误,就为nil, 不执行后续流程)
    //print(try! t.jsonMap()) // try!: 对代码绝对自信,一定不会发生错误(如果发生,直接crash)
    

    分析:

    1. JSONMapError枚举遵守Error协议
    2. jsonMap函数的return 错误,全部改为throw抛出错误,只有正常结果才使用return返回。
    3. 使用throw抛出错误会发现,函数需要使用throws标注,这是告诉使用者,这个函数可能抛出error,需要开发者决定是否处理。
    4. 使用throw后,发现不能直接调用jsonMap,需要使用try关键字来尝试调用。
    • try什么意思呢?如何使用呢?

    try有三种使用方式:

    • try : 向上甩锅,将错误抛给上层函数处理(最上层都没处理,就crash
    • try?: 只关注正常结果忽略所有错误(如果错误,就为nil, 不执行后续流程)
    • try!: 程序员对代码绝对自信一定不会发生错误(如果发生,直接crash
    • 上面三种方式,只是说到了抛出不管,那如何正确处理throw抛出的错误呢?

    do...catch

    • 我们可以通过do...catch来处理。其中do处理正确结果catch处理error,catch有个隐藏参数,就是error(Error类型)
    do {
        // 处理正常结果
        try t.jsonMap()   // 这里try不会报错了,因为错误都分给了catch
    } catch {
        // 处理错误类型(其中error为Error类型)
        print(error)
    }
    
    • 上面案例中,HTPerosn内的nameage的类型都未遵循``CustomJSONMap协议,所以会抛出error

      image.png
    • 当前使用throw,成功将错误正常结果进行分离,并了解如何通过trydo-catch对结果进行处理

    3.2 LocalizedError

    • 正常业务开发中,我们有时候并不满足于知道错误类型,我还希望得到关于这个错误的描述,以及其他信息
      (比如网络错误,我们希望获取error能记录errCodeerrMsg等信息)

    • 系统基于Error协议,再公开了一个LocalizedError协议。

    public protocol LocalizedError : Error {
        /// 错误的描述
        var errorDescription: String? { get }
        /// 错误的原因
        var failureReason: String? { get }
        /// 恢复的建议
        var recoverySuggestion: String? { get }
        /// 可提供的帮助
        var helpAnchor: String? { get }
    }
    
    • 所以我们可使用LocalizedError,把错误的信息表达得更详细一些。
      枚举类型的错误,可以使用switch每个属性都独立error错误描述

      image.png
    • 我们知道LocalizedError就是遵守Error的一个协议。在业务开发中,我们完全可仿照LocalizedError思维,直接自定义一个协议遵守Error协议。然后自己写一些属性函数等。

    3.3 CustomError (继承自Error)

    • OC中,系统提供了NSError错误类,NSError遵守Error协议,可以携带更多错误信息
    open class NSError : NSObject, NSCopying, NSSecureCoding {
    
        public init(domain: String, code: Int, userInfo dict: [String : Any]? = nil)
    
        open var domain: String { get }
        open var code: Int { get }
        open var userInfo: [String : Any] { get }
        open var localizedDescription: String { get }
        open var localizedFailureReason: String? { get }
        open var localizedRecoverySuggestion: String? { get }
        open var recoveryAttempter: Any? { get }
        open var helpAnchor: String? { get }
    }
    // NSError遵守Error协议
    extension NSError : Error { }
    
    • 在我们swift中,对接OCNSError的是CustomNSError。结构都一样。
    public protocol CustomNSError : Error {
        static var errorDomain: String { get }
        var errorCode: Int { get }
        var errorUserInfo: [String : Any] { get }
    }
    
    • 其实了解到这,我相信你,只要基于Error协议,你想要的,都可自定义协议实现

    3.4 rethorws

    当我们把函数作为另一个函数入参时,如果入参函数包含throws声明,不处理报错

    image.png
    • 【处理方式一】在函数内处理错误情况,外部可直接调用函数

      image.png
    • 【处理方式二】如果不想函数内处理错误情况,可以加上rethorws声明,由外部处理

      image.png

    rethrows适用于链式调用时,函数内部不需要关心异常情况最终统一处理异常情况。

    4. defer(延后处理)

    • defer:用来定义以任何方式(throw、return)离开代码块必须执行代码

      image.png
    • 多个defer时,执行顺序反序执行(与创建顺序相反)。

      image.png

    5. assert(断言)

    • 很多编程语言都有断言机制不符合指定条件就抛出运行时错误,常用于调试(Debug)阶段的条件判断
    image.png
    • 默认情况下,Swift断言只会在debug模式下生效release模式下忽略
      image.png

    本节内容较多,涉及到Mirror的深层探索,放到下一节:Mirror源码探索

    相关文章

      网友评论

        本文标题:swift进阶九:Mirror反射、Protocol、Error

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