Swift底层探索(七)Swift的反射Mirror的实现和错误处理

2,649 阅读5分钟

我正在参加「掘金·启航计划」

本文主要对Swift的反射实现有一个基本的认识,通过JSON解析来认识反射以及错误的实际使用。

主要内容

  1. Mirror的使用
  2. 错误Error的使用

1、 Mirror

Swift是一门静态语言,没有像OC中Runtime那样的运行时操作。但是Swift标准库中提供了反射机制,用来动态访问成员信息,这就是Mirror。(当然远没有Runtime那样强大)

1.1 基本使用

代码:

/*
 1、使用
 */
class WYTeacher: NSObject {
    var age: Int = 18
}
var t = WYTeacher()
let mirror = Mirror(reflecting: t.self)
for pro in mirror.children{
    print("\(String(describing: pro.label)): \(pro.value)")
}

//Optional("age"): 18

说明:

  • 传入的是一个对象
  • 获取到MIrror对象,通过它就可以获取到t对象的成员,分别通过label、value获取属性和值

1.2 Mirror认识

Mirror的初始化:

public init(reflecting subject: Any)

说明:

  • 进入Mirror初始化方法,发现传入的类型是Any,
  • 正是这个原因,我们才可以直接传t
  • 创建在给定实例上进行反射的反射对象
  • 如果subject的类型遵守CustomReflectable协议,生成的mirror由customMirror属性决定

children

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

说明:

  • children的类型为Children
  • 而Children是AnyCollection<Mirror.Child>的别名,是所有Child的集合
  • Child又是一个元组,由label、value组成
  • 分别用来存储属性和值

2、JOSN的实现

通过反射可以很轻松的获取到一个对象的成员,因此我们可以用来进行JSON解析,将一个对象转换成JSON格式

代码:

/*
 2、JSON解析
 */
//Int、String遵守协议
extension Int: CustomJSONMap{}
extension String: CustomJSONMap{}

//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 WYTeacher: CustomJSONMap {
    var age = 18
    var name = "WY"
}

//使用
var t = WYTeacher()
print(t.jsonMap())

说明:

  • 专门定义一个用来进行JSON解析的协议,这样可以给你任意类型的对象进行解析,只需要这个类型遵守该协议即可
  • 这里的实现也就是拿到对象的成员,分别存储到字典中,之后返回
  • 这里进行了递归调用,因为有可能属性本身也是一个对象。以一个字典的形式再存储到这个key的value上
  • 类中属性的类型也需要遵守协议,因为这里涉及到递归调用

3、Error的使用

在上文对于错误的处理是仅仅进行打印,这样并不规范,Swift中也提供了Error的处理方式。

Error的认识可以看我的另一篇博客Swift语法(十)Error

3.1 Error

Swift中,提供了Error协议来标识当前应用程序发生错误的情况。struct、Class、enum都可以遵守该协议,自定义错误。

代码:

//定义错误类型
enum JSONMapError: Error{
    case emptyKey
    case notConformProtocol
}

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

var t = WYTeacher()
print(try t.jsonMap())

说明:

  • 定义一个Error的枚举
  • 可以使用throw抛出异常
  • 方法中通过throws将错误抛出
  • 可能抛出异常的函数需要使用try调用,避免编译报错
  • 但是仅仅使用try调用,没有进行处理,这样会闪退
  • 可以使用try ... catch进行错误处理。具体的处理可以看另一篇博客,这里不再赘述

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

说明:

  • LocalError协议提供了多种属性
  • 我们可以给协议的这些属性增加描述,这样在错误处理时就可以进行打印

使用: 此时不给Int和String遵守CustomJSONMap,因此会走.notConformProtocol的分支

//定义更详细的错误信息
extension JSONMapError: LocalizedError{
    var errorDescription: String?{
        switch self {
        case .emptyKey:
            return "key为空"
        case .notConformProtocol:
            return "没有遵守协议"
        }
    }
}

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

运行结果:

16622120021553.jpg

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

使用:

//CustomNSError相当于NSError
extension JSONMapError: CustomNSError{
    var errorCode: Int{
        switch self {
        case .emptyKey:
            return 404
        case .notConformProtocol:
            return 504
        }
    }
}

var t = WYTeacher()
do{
    try t.jsonMap()
}catch{
    print((error as? JSONMapError)?.errorCode)
}

运行结果:

16622120841871.jpg

4、总结

  • Swift使用Mirror实现反射功能,可以动态获取对象的属性
  • 通过Mirror可以实现JSON的解析
  • Error是swift中错误类型的基本协议
  • 其中LocalizedError、CustomNSError都遵守Error,他们可以提供更详细的信息
  • try调用可能发生错误信息的函数