Swift Mirror & Error

前言

上篇文章Swift 內(nèi)存管理 & Runtime講解了Runtime的一個(gè)應(yīng)用場(chǎng)景:Swift類(lèi)可繼承NSObject,配合使用@objc修飾符,讓OC端的Runtime API可調(diào)用Swift類(lèi)的方法與屬性,同時(shí)使用Dynamic修飾符,也可實(shí)現(xiàn)方法交換,只不過(guò)這個(gè)交換是在編譯期就確定的。

那么問(wèn)題來(lái)了,既然Swift是一門(mén)靜態(tài)語(yǔ)言,那到底它有沒(méi)有類(lèi)似OC這樣的Runtime運(yùn)行時(shí)機(jī)制呢?當(dāng)然有,就是本篇文章即將介紹的Mirror反射,雖然沒(méi)有OC的Runtime強(qiáng)大,但是也能在運(yùn)行時(shí)獲取對(duì)象的類(lèi)型成員變量,其中第三方庫(kù)HandyJSON就是基于Mirror的機(jī)制來(lái)實(shí)現(xiàn)的。

一、Mirror反射

什么是反射??? 可以動(dòng)態(tài)獲取類(lèi)型、成員信息,在運(yùn)行時(shí)可以調(diào)用方法、屬性等行為的特性。

老規(guī)矩,還是先上示例代碼??

class LGTeacher {
    var age: Int = 18
    var name: String = "Luoji"
}
let mirror = Mirror(reflecting: LGTeacher().self)
for pro in mirror.children{
    print("\(pro.label ?? ""): \(pro.value)")
}


上述代碼是Mirror的一個(gè)簡(jiǎn)單的應(yīng)用場(chǎng)景 ?? 通過(guò)reflecting初始化,接著通過(guò).children讀取屬性名與值。

1.1 Mirror定義

接下來(lái)我們看看Mirror相關(guān)文檔的API的定義??

  • init(reflecting: Any)

傳入的類(lèi)型是Any。

  • .children

    接著查看代碼??
    /// A collection of `Child` elements describing the structure of the
    /// reflected subject.
    public let children: Mirror.Children

繼續(xù)查看Children??

    /// The type used to represent substructure.
    ///
    /// When working with a mirror that reflects a bidirectional or random access
    /// collection, you may find it useful to "upgrade" instances of this type
    /// to `AnyBidirectionalCollection` or `AnyRandomAccessCollection`. For
    /// example, to display the last twenty children of a mirror if they can be
    /// accessed efficiently, you write the following code:
    ///
    ///     if let b = AnyBidirectionalCollection(someMirror.children) {
    ///         for element in b.suffix(20) {
    ///             print(element)
    ///         }
    ///     }
    public typealias Children = AnyCollection<Mirror.Child>

最后看Child??

    /// An element of the reflected instance's structure.
    ///
    /// When the `label` component in not `nil`, it may represent the name of a
    /// stored property or an active `enum` case. If you pass strings to the
    /// `descendant(_:_:)` method, labels are used for lookup.
    public typealias Child = (label: String?, value: Any)

所以,回到示例代碼,通過(guò)print("\(pro.label ?? ""): \(pro.value)")打印的就是keyvalue

1.2 JSON解析

既然Mirror可以解析出類(lèi)的屬性keyvalue,那它能做什么? ?? 第一時(shí)間聯(lián)想到的就是JSON解析。還是先看以下解析的實(shí)例代碼??

class LGPerson {
    var name = "Luoji"
    var age = 18
    var student = LGStudent() // 持有對(duì)象
}

class LGStudent {
    var score = 100.0
}

func test(_ obj: Any) -> Any{
    
    let mirror = Mirror(reflecting: obj)
    // 遞歸終止條件
    guard !mirror.children.isEmpty else { return obj }
    var keyValue: [String: Any] = [:] // 記錄屬性名和屬性內(nèi)容
    for children in mirror.children{
        if let keyName = children.label {
            keyValue[keyName] = test(children.value)  // 遞歸(model嵌套的場(chǎng)景)
        }else{
            print("children.label ")
        }
    }
    return keyValue
}
    
let t = LGPerson()
print(test(t))

代碼不難,也是通過(guò)reflecting初始化mirror對(duì)象,然后讀取其children屬性值,存儲(chǔ)到keyValue字典并返回。其中做了判空,以及遞歸處理(處理屬性也是model的場(chǎng)景)。

優(yōu)化一

上述的代碼,是在一個(gè)swift命名空間中聲明的解析方法,我們想讓其通用化,基于封裝的思想,自然的想到,將其抽離成一個(gè)協(xié)議,這樣讓每個(gè)model遵循該協(xié)議,那么model就具備了JSON解析的功能。廢話不多說(shuō),直接上代碼??

//1、定義一個(gè)JSON解析協(xié)議
protocol LGJSONMap {
    func jsonMap() -> Any
}
//2、在extension中實(shí)現(xiàn)jsonMap()解析方法
extension LGJSONMap {
    func jsonMap() -> Any{
        let mirror = Mirror(reflecting: self)
        //遞歸終止條件
        guard !mirror.children.isEmpty else {
            return self
        }
        //字典,用于存儲(chǔ)json數(shù)據(jù)
        var keyValue: [String: Any] = [:]
        //遍歷
        for children in mirror.children {
            if let value = children.value as? LGJSONMap {
                if let keyName = children.label {
                    //遞歸
                    keyValue[keyName] = value.jsonMap()
                }else{
                    print("key是nil")
                }
            }else{
                print("當(dāng)前-\(children.value)-沒(méi)有遵守協(xié)議")
            }
        }
        return keyValue
    }
}

//3、讓類(lèi)遵守協(xié)議(注意:類(lèi)中屬性的類(lèi)型也需要遵守協(xié)議,否則無(wú)法解析)
class LGPerson : LGJSONMap {
    var name = "Luoji"
    var age = 18
    var student = LGStudent() // 持有對(duì)象
}

class LGStudent : LGJSONMap {
    var score = 100.0
}

//調(diào)用
var t = LGPerson()
print(t.jsonMap())

運(yùn)行??

上圖可知,沒(méi)有成功,提示屬性并沒(méi)有遵循協(xié)議,那么根據(jù)錯(cuò)誤提示,我們修改代碼??

extension Int: LGJSONMap{}
extension String: LGJSONMap{}
extension Double: LGJSONMap{}

完美解決!這里將解析方法抽離成協(xié)議的方法,是一種封裝的思想。既然提到協(xié)議,那么我們?cè)夙槺阒v解下Swift中的Protocol協(xié)議的用法吧。

Protocol

Swift的協(xié)議OC的協(xié)議功能多多了,非常強(qiáng)大,不僅可以聲明屬性、函數(shù),支持可選實(shí)現(xiàn),還可以在extension中完成屬性函數(shù)默認(rèn)實(shí)現(xiàn)(上述JSON解析就是這樣的場(chǎng)景)。

所以,以后大家在開(kāi)發(fā)過(guò)程中,如果碰到了類(lèi)似的這樣的場(chǎng)景,也可以封裝成協(xié)議的方法,很好的隔離代碼,而且讓代碼看起來(lái)非常簡(jiǎn)潔。??

遇到一些通用功能的函數(shù),我們可以使用協(xié)議封裝起來(lái),只要遵循這個(gè)協(xié)議,就可以直接調(diào)用相應(yīng)的屬性和函數(shù)。

優(yōu)化二

上述解析方法中,對(duì)于錯(cuò)誤的處理方式,只是簡(jiǎn)單的print一下,那有沒(méi)有一種更好的方式,將這些錯(cuò)誤封裝成一個(gè)對(duì)象,并拋出來(lái)給調(diào)用方,讓調(diào)用方自行處理呢?當(dāng)然有??

可以通過(guò)系統(tǒng)Error協(xié)議搭配throw關(guān)鍵字,優(yōu)雅的拋出錯(cuò)誤或返回常規(guī)結(jié)果,讓開(kāi)發(fā)者自己選擇處理方式。

二、Error處理

在Swift中,系統(tǒng)提供Error協(xié)議來(lái)表示當(dāng)前應(yīng)用程序發(fā)生錯(cuò)誤的情況,并支持使用throw關(guān)鍵字,優(yōu)雅的拋出錯(cuò)誤。

2.1 Error協(xié)議

public protocol Error {
}

上面源碼可知 ?? Error是一個(gè)協(xié)議,其中沒(méi)有任何實(shí)現(xiàn),這也就意味著你可以遵守該協(xié)議,然后自定義錯(cuò)誤類(lèi)型。所以不管是Struct、Classenum,我們都可以遵循這個(gè)Error協(xié)議來(lái)表示一個(gè)錯(cuò)誤。我們就先用枚舉enum來(lái)標(biāo)識(shí)錯(cuò)誤(枚舉的功能也十分強(qiáng)大,后續(xù)會(huì)有專(zhuān)門(mén)的一篇文章來(lái)講解)。

  1. 先定義錯(cuò)誤類(lèi)型
enum LGJSONMapError: Error{
    case emptyKey
    case notConformProtocol
}
  1. 然后調(diào)用
extension LGJSONMap{
    func jsonMap() -> Any{
        let mirror = Mirror(reflecting: self)
        //遞歸終止條件
        guard !mirror.children.isEmpty else {
            return self
        }
        //字典,用于存儲(chǔ)json數(shù)據(jù)
        var keyValue: [String: Any] = [:]
        //遍歷
        for children in mirror.children {
            if let value = children.value as? LGJSONMap {
                if let keyName = children.label {
                    //遞歸
                    keyValue[keyName] = value.jsonMap()
                }else{
                    return LGJSONMapError.emptyKey
                }
            }else{
                return LGJSONMapError.notConformProtocol
            }
        }
        return keyValue
    }
}

但是有個(gè)問(wèn)題,現(xiàn)在return的數(shù)據(jù),無(wú)法區(qū)分是正常的dic還是錯(cuò)誤枚舉,如何處理呢??? 可以利用throw關(guān)鍵字拋出錯(cuò)誤??

extension LGJSONMap{
    func jsonMap() -> Any{
        let mirror = Mirror(reflecting: self)
        //遞歸終止條件
        guard !mirror.children.isEmpty else {
            return self
        }
        //字典,用于存儲(chǔ)json數(shù)據(jù)
        var keyValue: [String: Any] = [:]
        //遍歷
        for children in mirror.children {
            if let value = children.value as? LGJSONMap {
                if let keyName = children.label {
                    //遞歸
                    keyValue[keyName] = value.jsonMap()
                }else{
                    throw LGJSONMapError.emptyKey
                }
            }else{
                throw LGJSONMapError.notConformProtocol
            }
        }
        return keyValue
    }
}

return改成throw后,編譯器會(huì)報(bào)錯(cuò)??

根據(jù)錯(cuò)誤的提示信息,是因?yàn)槲覀兟暶鞯暮瘮?shù)沒(méi)有聲明throw,那么我們修改函數(shù)聲明??

protocol LGJSONMap {
    func jsonMap() throws ->  Any
}

仍舊會(huì)報(bào)錯(cuò),用了throw沒(méi)有用try,那么補(bǔ)齊??

我們把屬性遵循協(xié)議的注釋掉,運(yùn)行看看是否會(huì)拋出錯(cuò)誤??

果然拋出了LGJSONMapError.notConformProtocol錯(cuò)誤。

Error處理的方式

Swift中錯(cuò)誤處理的方式主要有以下兩種:

  1. 使用try - throw
    最簡(jiǎn)便的,即甩鍋,將這個(gè)拋出給別人(向上拋出,拋給上層調(diào)用方函數(shù))。
    但是需要注意以下兩點(diǎn):
    • try? ?? 返回一個(gè)可選類(lèi)型,只有兩種結(jié)果:要么成功,返回具體的;要么錯(cuò)誤,但并不關(guān)心是哪種錯(cuò)誤,統(tǒng)一返回nil
print(try? t.jsonMap())
  • try! ?? 表示你對(duì)這段代碼有絕對(duì)的自信,這行代碼絕對(duì)不會(huì)發(fā)生錯(cuò)誤
print(try! t.jsonMap())

從上面可以知道,錯(cuò)誤是向上拋出的,即拋給了調(diào)用函數(shù),如果調(diào)用的上層函數(shù)也不處理,則直接拋給main,main沒(méi)有辦法處理,則直接報(bào)錯(cuò)。

  1. 使用do - catch
    其中do處理正確結(jié)果,catch處理error,catch有個(gè)隱藏參數(shù),就是error(Error類(lèi)型)
do {
    // 處理正常結(jié)果
    try t.jsonMap()   // 這里try不會(huì)報(bào)錯(cuò)了,因?yàn)殄e(cuò)誤都分給了catch
} catch {
    // 處理錯(cuò)誤類(lèi)型(其中error為Error類(lèi)型)
    print(error)
}
LocalizedError

上述的錯(cuò)誤處理中,只知道了錯(cuò)誤類(lèi)型,如果還想知道錯(cuò)誤相關(guān)的描述其它信息,則需要使用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 }
}

接下來(lái),我們來(lái)使用一下這個(gè)協(xié)議,修改上面的解析代碼??

//定義錯(cuò)誤類(lèi)型
enum LGJSONMapError: Error{
    case emptyKey
    case notConformProtocol
}

extension LGJSONMapError : LocalizedError {
    var errorDescription: String?{
            switch self {
            case .emptyKey:
                return "key為空"
            case .notConformProtocol:
                return "沒(méi)有遵守協(xié)議"
            }
        }
}

//調(diào)用
var t = LGPerson()
do {
    // 處理正常結(jié)果
    try t.jsonMap()   // 這里try不會(huì)報(bào)錯(cuò)了,因?yàn)殄e(cuò)誤都分給了catch
} catch {
    // 處理錯(cuò)誤類(lèi)型(其中error為Error類(lèi)型)
    print(error.localizedDescription)
}

完美,錯(cuò)誤描述被打印了出來(lái)!

CustomNSError協(xié)議

還有一個(gè)CustomNSError協(xié)議,相當(dāng)于OC中的NSError,其定義如下,有三個(gè)默認(rèn)屬性??

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

接著我們繼續(xù)修改JSON解析中的LGJSONMapError,讓其遵守CustomNSError協(xié)議??

extension LGJSONMapError: CustomNSError{
    var errorCode: Int{
        switch self {
        case .emptyKey:
            return 404
        case .notConformProtocol:
            return 504
        }
    }
}

//調(diào)用
var t = LGPerson()
do {
    // 處理正常結(jié)果
    try t.jsonMap()   // 這里try不會(huì)報(bào)錯(cuò)了,因?yàn)殄e(cuò)誤都分給了catch
} catch {
    // 處理錯(cuò)誤類(lèi)型(其中error為Error類(lèi)型)
//    print(error.localizedDescription)
    print((error as? LGJSONMapError)?.errorCode)
}
rethorws

rethrows是處理這種場(chǎng)景的錯(cuò)誤 ?? 函數(shù)1函數(shù)2入?yún)?/code>,其中函數(shù)1中有throws錯(cuò)誤。
上代碼

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

rethrows將函數(shù)參數(shù)add的錯(cuò)誤再次拋出,統(tǒng)一交由外部do - catch處理。

defer(延后處理)
func functionDefer()  {
    print("begin")
    defer {
        print("defer")
    }
    print("end")
}

函數(shù)執(zhí)行完成之前才會(huì)執(zhí)行defer代碼塊內(nèi)部的邏輯。如果有多個(gè)defer代碼塊,執(zhí)行順序怎么樣?

func functionDefer()  {
    print("begin")
    defer {
        print("defer1")
    }
    defer {
        print("defer2")
    }
    print("end")
}

defer代碼塊的執(zhí)行順序是逆序的。

assert(斷言)

很多編程語(yǔ)言都有斷言機(jī)制不符合指定條件拋出運(yùn)行時(shí)錯(cuò)誤,常用于調(diào)試(Debug)階段條件判斷。例如??

func devide(_ a: Int, _ b: Int) -> Int {
    assert(b != 0, "除數(shù)不能為0")
    return a / b
}

默認(rèn)情況下,Swift斷言只會(huì)在debug模式下生效,release模式下忽略??


總結(jié)

本篇文章主要利用Swift類(lèi)中的Mirror反射機(jī)制,封裝了一套JSON解析協(xié)議,同時(shí)將解析時(shí)錯(cuò)誤也進(jìn)行了封裝處理,順便講解了Error的幾個(gè)常用的協(xié)議。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容