swift進(jìn)階九:Mirror反射、Protocol、Error、defer、assert

swift進(jìn)階 學(xué)習(xí)大綱

上一節(jié),我們分析了閉包 & Runtime & Any等類(lèi)型

介紹Runtime運(yùn)行時(shí)時(shí),我們知道swift靜態(tài)語(yǔ)言,但可兼容OC類(lèi)實(shí)現(xiàn)objc_msgSend消息發(fā)送機(jī)制。

  • swiftdynamic聲明的函數(shù)可在extension中進(jìn)行函數(shù)替換。不過(guò)這個(gè)替換編譯期完成了。

那,swift有沒(méi)有自己的運(yùn)行時(shí)機(jī)制呢?

  • 有,Mirror反射機(jī)制。雖然沒(méi)OC運(yùn)行時(shí)那么強(qiáng)大。但支持運(yùn)行時(shí)獲取對(duì)象類(lèi)型成員變量HandyJSON就是基于Mirror思想,直接從內(nèi)存讀取來(lái)實(shí)現(xiàn)的??)

本節(jié),我們先體驗(yàn)一下Mirror,下一節(jié),我們深入研究Mirror底層原理

  1. Mirror體驗(yàn)
  2. protocol協(xié)議
  3. Error錯(cuò)誤機(jī)制(try、throws、rethrows)
  4. defer
  5. assert

1. Mirror體驗(yàn)

  • Mirror(反射),可以動(dòng)態(tài)獲取類(lèi)型成員變量,在運(yùn)行時(shí)可以調(diào)用方法屬性等行為的特性
    對(duì)于一個(gè)純swift類(lèi)來(lái)說(shuō),并不支持我們直接像OCRuntime那樣操作。但Swift標(biāo)準(zhǔn)庫(kù)給我們提供簡(jiǎn)單實(shí)用反射機(jī)制
  • 測(cè)試案例:
class HTPerson {
   var name = "ht"
   var age = 18
}

let p = HTPerson()
let mirror = Mirror(reflecting: p.self)

for pro in mirror.children {
   print("\(pro.label ?? ""):\(pro.value)")
}
  • 打印結(jié)果:
image.png
  • 進(jìn)入Mirror內(nèi)部可以看到:
  1. Mirror是一個(gè)struct結(jié)構(gòu)體

    image.png

  2. Mirror 初始化時(shí),入?yún)?code>Any類(lèi)型

    image.png

  3. children屬性為(label: String?, value: Any)元組類(lèi)型

    image.png

  • Mirror用途很多,最常用的是用于JSON解析
class HTPerson {
    var name = "ht"
    var age = 18
    var student = HTStudent() // 持有對(duì)象
}

class HTStudent {
    var double = 10.0
}

func test(_ obj: Any) -> Any{
    
    let mirror = Mirror(reflecting: obj)
    // 遞歸終止條件
    guard !mirror.children.isEmpty else { return obj }  
    var keyValue: [String: Any] = [:] // 記錄屬性名和屬性?xún)?nèi)容
    for children in mirror.children{
        if let keyName = children.label {
            keyValue[keyName] = test(children.value)  // 遞歸(繼續(xù)遍歷person中的對(duì)象屬性)
        }else{
            print("children.label ")
        }
    }
    return keyValue
}
    
let t = HTPerson()
print(test(t))
  • 打印結(jié)果:


    image.png

分析:

  1. mirror成功讀取對(duì)象屬性名屬性值,可使用字典存儲(chǔ)和輸出;

  2. 每個(gè)屬性都會(huì)默認(rèn)當(dāng)作對(duì)象來(lái)處理,通過(guò)mirror.children可判斷當(dāng)前對(duì)象是否擁有屬性,沒(méi)有就跳出當(dāng)前屬性遞歸流程。 這樣可保證所有讀取children完整層級(jí)信息

(ps:遞歸實(shí)際是利用特性,直接間接 調(diào)用自身實(shí)現(xiàn)層級(jí)讀取需求,遞歸需要有明確的終止條件)

  • 上面代碼有個(gè)不好的點(diǎn): 錯(cuò)誤結(jié)果print打印,外界不知道
    swift中,錯(cuò)誤信息一般使用Error進(jìn)行表示。

2. Protocol協(xié)議

  • 在介紹Error之前,我們先介紹一下Swift協(xié)議
    Swift協(xié)議非常強(qiáng)大,不僅可以聲明屬性函數(shù),支持可選實(shí)現(xiàn),還可以在extension中完成屬性函數(shù)默認(rèn)實(shí)現(xiàn)

  • 以上面案例為例:

如果每次讀取屬性,我們都需要調(diào)用test函數(shù),會(huì)顯得非常麻煩

  • 協(xié)議可以幫我們默認(rèn)實(shí)現(xiàn)這個(gè)方法,只要對(duì)象遵守這個(gè)協(xié)議,都可以直接使用

【思路】

  1. 創(chuàng)建一個(gè)協(xié)議(CustomJSONMap),聲明jsonMap函數(shù),并在extension中默認(rèn)實(shí)現(xiàn)jsonMap
  2. 創(chuàng)建一個(gè)枚舉JSONMapError,對(duì)所有錯(cuò)誤類(lèi)型進(jìn)行列舉
  3. jsonMap內(nèi)部支持Mirror所有遵循CustomJSONMap協(xié)議的屬性。對(duì)未遵循協(xié)議的屬性和屬性名為空的屬性返回相應(yīng)的錯(cuò)誤類(lèi)型
// 錯(cuò)誤枚舉
enum JSONMapError {
    case emptyError         // 空
    case notConformProtocol // 未遵守協(xié)議
}

// 協(xié)議(自帶jsonMap函數(shù))
protocol CustomJSONMap {
    func jsonMap() ->Any
}

// extension中默認(rèn)jsonMap實(shí)現(xiàn)
extension CustomJSONMap {
    
    func jsonMap() -> Any {

        let mirror = Mirror(reflecting: self)
        
        guard !mirror.children.isEmpty else { return self }
        
        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.emptyError      // 屬性名為空
                }
                
            } else {
                return JSONMapError.notConformProtocol  // children未遵守CustomJSONMap協(xié)議
            }
        }
        return keyValue
    }
}

class HTPerson: CustomJSONMap {
    var name = "ht"
    var age = 18
}

let t = HTPerson()
print(t.jsonMap())
  • 運(yùn)行上述案例,可以看到打印了notConformProtocol
    因?yàn)?code>name和age屬性分別是StringInt類(lèi)型,他們沒(méi)有遵守CustomJSONMap協(xié)議。

    image.png

  • 既然如此,我們讓StringInt都遵守CustomJSONMap協(xié)議,再運(yùn)行。

extension String: CustomJSONMap {}
extension Int: CustomJSONMap {}
image.png
  • 完美打印所有內(nèi)容

在我們的開(kāi)發(fā)中,protocol協(xié)議一大利器,使用非常方便。

  • 遇到一些通用函數(shù),我們可以使用協(xié)議包裝起來(lái),只要遵循這個(gè)協(xié)議,就可以直接調(diào)用相應(yīng)的屬性函數(shù)。很好的隔離代碼,而且讓代碼看起來(lái)非常簡(jiǎn)潔
  • 上面的錯(cuò)誤信息,我們只是使用枚舉統(tǒng)一羅列return時(shí)常規(guī)結(jié)果錯(cuò)誤類(lèi)型混在一起返回,讓我們很不舒服

如何解決?

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

3. Error機(jī)制(try、throws、rethrows)

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

3.1 基礎(chǔ)Error協(xié)議

public protocol Error { }
  • 系統(tǒng)的Error協(xié)議,就是一個(gè)空的公共協(xié)議, 不管是classstruct還是enum,都可以通過(guò)遵守這個(gè)協(xié)議,來(lái)表達(dá)錯(cuò)誤

enum為例:

// 錯(cuò)誤枚舉,遵循Error
enum JSONMapError: Error {
    case emptyError         // 空
    case notConformProtocol // 未遵守協(xié)議
}

// 協(xié)議(自帶jsonMap函數(shù))
protocol CustomJSONMap {
    func jsonMap() throws ->Any
}

// extension中默認(rèn)jsonMap實(shí)現(xiàn)
extension CustomJSONMap {
    
    func jsonMap() throws -> Any {
        let mirror = Mirror(reflecting: self)
        
        guard !mirror.children.isEmpty else { return self }
        
        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() // 加了throws后,需要使用try執(zhí)行 jsonmap()
                } else {
                    throw JSONMapError.emptyError           // 屬性名為空
                }
            } else {
                throw JSONMapError.notConformProtocol       // children未遵守CustomJSONMap協(xié)議
            }
        }
        return keyValue
    }
}

class HTPerson: CustomJSONMap {
    var name = "ht"     // String未繼承CustomJSONMap,會(huì)error
    var age = 18        // Int未繼承CustomJSONMap,會(huì)error
}

let t = HTPerson()

// do...catch 分開(kāi)處理,catch中有默認(rèn)參數(shù)error。
do {
    // 處理正常結(jié)果
    print(try t.jsonMap())
} catch {
    // 處理錯(cuò)誤類(lèi)型(其中error為Error類(lèi)型)
    print(error)
}

//print(try t.jsonMap())    // try : 向上甩鍋,將錯(cuò)誤拋給上層函數(shù)處理(上層沒(méi)處理,就crash)
print(try? t.jsonMap())   // try?: 只關(guān)注正常結(jié)果,忽略錯(cuò)誤(如果錯(cuò)誤,就為nil, 不執(zhí)行后續(xù)流程)
//print(try! t.jsonMap()) // try!: 對(duì)代碼絕對(duì)自信,一定不會(huì)發(fā)生錯(cuò)誤(如果發(fā)生,直接crash)

分析:

  1. JSONMapError枚舉遵守Error協(xié)議
  2. jsonMap函數(shù)的return 錯(cuò)誤,全部改為throw拋出錯(cuò)誤,只有正常結(jié)果才使用return返回。
  3. 使用throw拋出錯(cuò)誤會(huì)發(fā)現(xiàn),函數(shù)需要使用throws標(biāo)注,這是告訴使用者,這個(gè)函數(shù)可能拋出error,需要開(kāi)發(fā)者決定是否處理。
  4. 使用throw后,發(fā)現(xiàn)不能直接調(diào)用jsonMap,需要使用try關(guān)鍵字來(lái)嘗試調(diào)用。
  • try什么意思呢?如何使用呢?

try有三種使用方式:

  • try : 向上甩鍋,將錯(cuò)誤拋給上層函數(shù)處理(最上層都沒(méi)處理,就crash
  • try?: 只關(guān)注正常結(jié)果忽略所有錯(cuò)誤(如果錯(cuò)誤,就為nil, 不執(zhí)行后續(xù)流程)
  • try!: 程序員對(duì)代碼絕對(duì)自信一定不會(huì)發(fā)生錯(cuò)誤(如果發(fā)生,直接crash
  • 上面三種方式,只是說(shuō)到了拋出不管,那如何正確處理throw拋出的錯(cuò)誤呢?

do...catch

  • 我們可以通過(guò)do...catch來(lái)處理。其中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)
}
  • 上面案例中,HTPerosn內(nèi)的nameage的類(lèi)型都未遵循``CustomJSONMap協(xié)議,所以會(huì)拋出error

    image.png

  • 當(dāng)前使用throw,成功將錯(cuò)誤正常結(jié)果進(jìn)行分離,并了解如何通過(guò)trydo-catch對(duì)結(jié)果進(jìn)行處理

3.2 LocalizedError

  • 正常業(yè)務(wù)開(kāi)發(fā)中,我們有時(shí)候并不滿(mǎn)足于知道錯(cuò)誤類(lèi)型,我還希望得到關(guān)于這個(gè)錯(cuò)誤的描述,以及其他信息
    (比如網(wǎng)絡(luò)錯(cuò)誤,我們希望獲取error能記錄errCodeerrMsg等信息)

  • 系統(tǒng)基于Error協(xié)議,再公開(kāi)了一個(gè)LocalizedError協(xié)議。

public protocol LocalizedError : Error {
    /// 錯(cuò)誤的描述
    var errorDescription: String? { get }
    /// 錯(cuò)誤的原因
    var failureReason: String? { get }
    /// 恢復(fù)的建議
    var recoverySuggestion: String? { get }
    /// 可提供的幫助
    var helpAnchor: String? { get }
}
  • 所以我們可使用LocalizedError,把錯(cuò)誤的信息表達(dá)得更詳細(xì)一些。
    枚舉類(lèi)型的錯(cuò)誤,可以使用switch每個(gè)屬性都獨(dú)立error錯(cuò)誤描述

    image.png

  • 我們知道LocalizedError就是遵守Error的一個(gè)協(xié)議。在業(yè)務(wù)開(kāi)發(fā)中,我們完全可仿照LocalizedError思維,直接自定義一個(gè)協(xié)議遵守Error協(xié)議。然后自己寫(xiě)一些屬性函數(shù)等。

3.3 CustomError (繼承自Error)

  • OC中,系統(tǒng)提供了NSError錯(cuò)誤類(lèi),NSError遵守Error協(xié)議,可以攜帶更多錯(cuò)誤信息
open class NSError : NSObject, NSCopying, NSSecureCoding {

    public init(domain: String, code: Int, userInfo dict: [String : Any]? = nil)

    open var domain: String { get }
    open var code: Int { get }
    open var userInfo: [String : Any] { get }
    open var localizedDescription: String { get }
    open var localizedFailureReason: String? { get }
    open var localizedRecoverySuggestion: String? { get }
    open var recoveryAttempter: Any? { get }
    open var helpAnchor: String? { get }
}
// NSError遵守Error協(xié)議
extension NSError : Error { }
  • 在我們swift中,對(duì)接OCNSError的是CustomNSError。結(jié)構(gòu)都一樣。
public protocol CustomNSError : Error {
    static var errorDomain: String { get }
    var errorCode: Int { get }
    var errorUserInfo: [String : Any] { get }
}
  • 其實(shí)了解到這,我相信你,只要基于Error協(xié)議,你想要的,都可自定義協(xié)議來(lái)實(shí)現(xiàn)

3.4 rethorws

當(dāng)我們把函數(shù)作為另一個(gè)函數(shù)入?yún)?/code>時(shí),如果入?yún)⒑瘮?shù)包含throws聲明,不處理會(huì)報(bào)錯(cuò)

image.png

  • 【處理方式一】在函數(shù)內(nèi)處理錯(cuò)誤情況,外部可直接調(diào)用函數(shù)

    image.png

  • 【處理方式二】如果不想函數(shù)內(nèi)處理錯(cuò)誤情況,可以加上rethorws聲明,由外部處理

    image.png

rethrows適用于鏈?zhǔn)秸{(diào)用時(shí),函數(shù)內(nèi)部不需要關(guān)心異常情況最終統(tǒng)一處理異常情況。

4. defer(延后處理)

  • defer:用來(lái)定義以任何方式(throw、return)離開(kāi)代碼塊必須執(zhí)行代碼

    image.png

  • 多個(gè)defer時(shí),執(zhí)行順序反序執(zhí)行(與創(chuàng)建順序相反)。

    image.png

5. assert(斷言)

  • 很多編程語(yǔ)言都有斷言機(jī)制不符合指定條件就拋出運(yùn)行時(shí)錯(cuò)誤,常用于調(diào)試(Debug)階段的條件判斷
image.png
  • 默認(rèn)情況下,Swift斷言只會(huì)在debug模式下生效release模式下忽略
    image.png

本節(jié)內(nèi)容較多,涉及到Mirror的深層探索,放到下一節(jié):Mirror源碼探索

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,362評(píng)論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,577評(píng)論 3 429
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 178,486評(píng)論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀(guān)的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 63,852評(píng)論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,600評(píng)論 6 412
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 55,944評(píng)論 1 328
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,944評(píng)論 3 447
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 43,108評(píng)論 0 290
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,652評(píng)論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,385評(píng)論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,616評(píng)論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,111評(píng)論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,798評(píng)論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 35,205評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 36,537評(píng)論 1 295
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,334評(píng)論 3 400
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,570評(píng)論 2 379

推薦閱讀更多精彩內(nèi)容