Swift Mirror & Error

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

//进入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)