Swift进阶05:反射&错误处理

第五节课:反射&错误处理

反射Mirror

反射就是可以动态获取类型、成员信息,再运行时可以调用方法、属性等行为的特性
上面我们分析过了,对于一个纯Swift类来说,并不支持我们直接像OC那样操作,但是Swift标准库依然提供了反射机制让我们访问成员信息,反射的用法很简单,我们熟悉一下:

class HZMTeacher{
    var age = 18
    var double = 1.85
}
var t = HZMTeacher()

let mirror = Mirror(reflecting: HZMTeacher.self)

for pro in mirror.children{
    print("\(pro.label!):\(pro.value)")
}

结果并没有打印,为什么?是因为Mirror中传入的参数不对,应该是传入实例对象,修改如下

Mirror01.png

查看一下Mirror的定义

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

public init(reflecting subject: Any)

继续往下看

public let children: Mirror.Children

//Child,发现是一个元组类型,由可选的标签和值构成,
public typealias Child = (label: String?, value: Any)

//Children,发现是一个AnyCollection,接收一个泛型
public typealias Children = AnyCollection<Mirror.Child>

这也是解释了为什么可以够通过label、value打印的原因。即可以在编译时期且不用知道任何类型信息情况下,在Child的值上用Mirror去遍历整个对象的层级。

JSON解析

根据Mirror的这个特性,能通过Mirror做什么?首先想到的是JSON解析,看下面代码

class HZMTeacher{
    var age = 18
    var name = "HZM"
}

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
}

let t = HZMTeacher()
print(test(t))

打印如下


Mirror02.png

JSON解析封装

如果我们想大规模的使用上述的JSON解析,上面只是针对HZMTeacher的JSON解析,所以,为了方便其他类使用,可以将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 HZMTeacher: CustomJSONMap {
    var age = 18
    var name = "HZM"
}

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

注意:运行代码发现,打印没有遵守协议,原因是因为HZMTeacher中属性的类型也需要遵守CustomJSONMap协议

添加一下代码

//Int、String遵守协议
extension Int: CustomJSONMap{}
extension String: CustomJSONMap{}
Mirror03.png

可以看到,输出了我们想要的东西

错误处理

为了让我们自己封装的JSON解析更好用,除了对正常返回的处理,还需要对其中的错误进行处理,在上面的封装中,我们采用的是print打印的,这样并不规范,也不好维护及管理

这里我们来通过Swift中的错误处理来合理表达一个错误:
Swift提供Error协议来标识当前应用程序发生错误的情况,Error的定义如下

public protocol Error {
}

Error是一个空协议,没有任何实现,这也就意味着你可以遵守该协议,然后自定义错误类型。所以不管是我们的struct、Class、enum,我们都可以遵循这个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抛出)

//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{
                    throw JSONMapError.emptyKey
                }
            }else{
                throw JSONMapError.notConformProtocol
            }
        }
        return keyValue
    }
}

发现改成throw抛出错误后,编译器提示有错,其原因是因为方法并没有声明成throws

错误处理01.png

所以还需要在方法的返回值箭头前增加throws(表示将方法中错误抛出),修改后的默认实现代码如下所示

//1、定义一个JSON解析协议
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] = value.jsonMap()
                }else{
                    throw JSONMapError.emptyKey
                }
            }else{
                throw JSONMapError.notConformProtocol
            }
        }
        return keyValue
    }
}

由于我们在jsonMap方法中递归调用了自己,所以还需要在递归调用前增加 try 关键字

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

<!--使用时需要加上try-->
var t = HZMTeacher()
print(try t.jsonMap())

这样就简单的写了一个错误表达式

swift中错误处理的方式

swift中错误处理的方式主要有以下两种:
第一种:使用try关键字,是最简便的,即甩锅,将这个抛出给别人(向上抛出,抛给上层函数)。但是在使用时,需要注意以下两点:

  • try? 返回一个可选类型,只有两种结果:
    要么成功,返回具体的字典值
    要么错误,但并不关心是哪种错误,统一返回nil
    错误处理02.png
  • try! 表示你对这段代码有绝对的自信,这行代码绝对不会发生错误
    错误处理03.png

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

//使用
var t = HZMTeacher()

func test() throws -> Any{
    try t.jsonMap()
}
try test()
错误处理05.png

第二种:使用do...catch

错误处理04.png

LocalError协议

如果只是用Error还不足以表达更详尽的错误信息,只能表达单一的错误类型,如果我想把错误信息以String的形式关联上,可以使用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 }
}

继续用上面的例子做修改,添加如下代码

extension JSONMapError: LocalizedError{
    var errorDescription: String?{
        switch self {
        case .emptyKey:
            return "emptyKey"
        case .notConformProtocol:
           return "notConformProtocol"
        }
    }
}
错误处理06.png

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

修改JSON解析中的JSONMapError,让其遵守CustomNSError协议,如下所示

//CustomNSError相当于NSError
extension JSONMapError: CustomNSError{
    var errorCode: Int{
        switch self {
        case .emptyKey:
            return 404
        case .notConformProtocol:
            return 504
        }
    }
}
错误处理07.png

总结一下

Error是swift中错误类型的基本协议,其中LocalizedErrorCustomNSError都遵守Error

如果在方法中,想要同时返回正常值和错误,需要通过throw返回错误,并且在方法返回值的箭头前面加throws关键字,再调用方法时,还需要加上try关键字,或者使用do-catch,使用try时,有以下两点需要注意:

  • try?返回的是一个可选类型,要么成功,返回正常值,要么失败,返回nil

  • try! 表示你对自己的代码非常自信,绝对不会发生错误,一旦发生错误,就会崩溃

使用建议:建议使用try?

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容