swift进阶

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

2020-12-18  本文已影响0人  markhetao

swift进阶 学习大纲

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

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

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

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

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

1. Mirror体验

  • 测试案例:
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
  1. Mirror是一个struct结构体

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

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

    image.png
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))

分析:

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

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

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

2. Protocol协议

如果每次读取属性,我们都需要调用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())
extension String: CustomJSONMap {}
extension Int: CustomJSONMap {}
image.png

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

  • 遇到一些通用函数,我们可以使用协议包装起来,只要遵循这个协议,就可以直接调用相应的属性函数。很好的隔离代码,而且让代码看起来非常简洁

如何解决?

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

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

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

3.1 基础Error协议

public protocol Error { }

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 : 向上甩锅,将错误抛给上层函数处理(最上层都没处理,就crash
  • try?: 只关注正常结果忽略所有错误(如果错误,就为nil, 不执行后续流程)
  • try!: 程序员对代码绝对自信一定不会发生错误(如果发生,直接crash

do...catch

  • 我们可以通过do...catch来处理。其中do处理正确结果catch处理error,catch有个隐藏参数,就是error(Error类型)
do {
    // 处理正常结果
    try t.jsonMap()   // 这里try不会报错了,因为错误都分给了catch
} catch {
    // 处理错误类型(其中error为Error类型)
    print(error)
}

3.2 LocalizedError

public protocol LocalizedError : Error {
    /// 错误的描述
    var errorDescription: String? { get }
    /// 错误的原因
    var failureReason: String? { get }
    /// 恢复的建议
    var recoverySuggestion: String? { get }
    /// 可提供的帮助
    var helpAnchor: String? { get }
}

3.3 CustomError (继承自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 { }
public protocol CustomNSError : Error {
    static var errorDomain: String { get }
    var errorCode: Int { get }
    var errorUserInfo: [String : Any] { get }
}

3.4 rethorws

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

image.png

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

4. defer(延后处理)

5. assert(断言)

image.png

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

上一篇下一篇

猜你喜欢

热点阅读