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<Mirror.Child>
👇
//进入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代码块的执行顺序是逆序的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容