利用Swift的反射機(jī)制遍歷對(duì)象屬性

閱讀原文

前段時(shí)間寫了一個(gè)macOS圖床小應(yīng)用cuImage考阱,里面有些技術(shù)點(diǎn)一直沒(méi)抽出時(shí)間總結(jié)和分享葬凳。臨近畢業(yè)事情也比較多突琳,只能擠時(shí)間了他巨。本文將利用Swift的反射機(jī)制遍歷對(duì)象屬性,從而簡(jiǎn)化代碼蒂秘,提高代碼復(fù)用率泽本。

cuImage中每個(gè)圖床都有相應(yīng)的配置信息,如七牛云的圖床配置信息QiniuHostInfo材彪。我希望將其通過(guò)UserDefaults保存起來(lái)。(圖床中有些敏感信息需要加密琴儿,但為了簡(jiǎn)化描述段化,本文就不涉及加密相關(guān)的點(diǎn)了,其實(shí)加密方面我也是小白菜造成。至于為什么不用Keychain加密就是另一回事了显熏,這里略過(guò)。)

由于UserDefaults不支持自定義對(duì)象晒屎,如果強(qiáng)行直接保存喘蟆,會(huì)在運(yùn)行時(shí)拋出異常:

Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Attempt to insert non-property list object 'Abc' for key 'Xyz'.

為了讓自定義對(duì)象也能夠通過(guò)UserDefaults進(jìn)行持久化存儲(chǔ),需要通過(guò)NSKeyedArchiver將自定義對(duì)象編碼成NSData格式鼓鲁。這就需要遵循NSCoding協(xié)議蕴轨,實(shí)現(xiàn)該協(xié)議聲明的兩個(gè)方法(init(_:)encode(_:))。一般的實(shí)現(xiàn)方式如下:

final class QiniuHostInfo: NSObject, NSCoding {
    var name = ""
    var accessKey = ""
    var secretKey = ""
    
    init(name: String = "", accessKey: String = "", secretKey: String = "") {
        self.name = name
        self.accessKey = accessKey
        self.secretKey = secretKey
        
        super.init()
    }
    
    init?(coder aDecoder: NSCoder) {
        name = aDecoder.decodeObject(forKey: #keyPath(name)) as! String
        accessKey = aDecoder.decodeObject(forKey: #keyPath(accessKey)) as! String
        secretKey = aDecoder.decodeObject(forKey: #keyPath(secretKey)) as! String
    }
    
    func encode(with aCoder: NSCoder) {
        aCoder.encode(name, forKey: #keyPath(name))
        aCoder.encode(accessKey, forKey: #keyPath(accessKey))
        aCoder.encode(secretKey, forKey: #keyPath(secretKey))
    }
}

final class ImgurHostInfo: NSObject, NSCoding {
    var name = ""
    var userName = ""
    var password = ""
    
    init(name: String = "", userName: String = "", password: String = "") {
        self.name = name
        self.userName = userName
        self.password = password
        
        super.init()
    }
    
    init?(coder aDecoder: NSCoder) {
        name = aDecoder.decodeObject(forKey: #keyPath(name)) as! String
        userName = aDecoder.decodeObject(forKey: #keyPath(userName)) as! String
        password = aDecoder.decodeObject(forKey: #keyPath(password)) as! String
    }
    
    func encode(with aCoder: NSCoder) {
        aCoder.encode(name, forKey: #keyPath(name))
        aCoder.encode(userName, forKey: #keyPath(userName))
        aCoder.encode(password, forKey: #keyPath(password))
    }
}

這種方式的缺點(diǎn)在于骇吭,如果QiniuHostInfo增加新的屬性或者重構(gòu)代碼時(shí)修改屬性橙弱,需要在NSCoding聲明的兩個(gè)方法中都添加或修改對(duì)應(yīng)的代碼,還可能有疏漏燥狰。而且棘脐,當(dāng)需要新增一個(gè)圖床(如ImgurHostInfo)時(shí),又得按相同的的步驟擼一遍代碼龙致。

下面是我最終采用的方式蛀缝。首先,我為各種圖床信息類寫了個(gè)公共基類HostInfo目代,將實(shí)現(xiàn)NSCoding協(xié)議方法的任務(wù)交給了基類HostInfo屈梁,一次性搞定嗤练。以后每增加一個(gè)圖床,只需要在相應(yīng)的圖床信息子類(如QiniuHostInfo, ImgurHostInfo)定義好自身的屬性就行了俘闯,這樣子類的代碼就清爽多了潭苞。

class HostInfo: NSObject, NSCoding {
    var name = ""

    override init() {
        super.init()
    }
    
    convenience required init?(coder aDecoder: NSCoder) {
        self.init()
        
        forEachChildOfMirror(reflecting: self) { key in
            setValue(aDecoder.decodeObject(forKey: key), forKey: key)
        }
    }
    
    func encode(with aCoder: NSCoder) {
        forEachChildOfMirror(reflecting: self) { key in
            aCoder.encode(value(forKey: key), forKey: key)
        }
    }
    
    func forEachChildOfMirror(reflecting subject: Any, handler: (String) -> Void) {
        var mirror: Mirror? = Mirror(reflecting: subject)
        while mirror != nil {
            for child in mirror!.children {
                if let key = child.label {
                    handler(key)
                }
            }
            
            // Get super class's properties.
            mirror = mirror!.superclassMirror
        }
    }
}

final class QiniuHostInfo: HostInfo {
    var accessKey = ""
    var secretKey = ""
}

final class ImgurHostInfo: HostInfo {
    var userName = ""
    var password = ""
}

上面的代碼中,init(_:)encode(_:)都需要用到反射真朗,所以我將公共部分抽出來(lái)此疹,并把每訪問(wèn)到一個(gè)屬性時(shí)要做的操作以閉包的形式傳給了forEachChildOfMirror(_:_:)函數(shù)。其中用到了superclassMirror是因?yàn)樵诟割愔锌赡芤灿幸恍┕矊傩孕枰槐闅v到遮婶,如HostInfo中的name屬性蝗碎。于是遍歷完當(dāng)前類的屬性后,就可以通過(guò)superclassMirror順著繼承鏈往上爬旗扑,訪問(wèn)到繼承連上的類的所有屬性了蹦骑。

如果讀者們有其他不錯(cuò)的實(shí)現(xiàn)方式,期待你們的分享臀防。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末眠菇,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子袱衷,更是在濱河造成了極大的恐慌捎废,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,104評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件致燥,死亡現(xiàn)場(chǎng)離奇詭異登疗,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)嫌蚤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門辐益,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人脱吱,你說(shuō)我怎么就攤上這事智政。” “怎么了箱蝠?”我有些...
    開封第一講書人閱讀 168,697評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵女仰,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我抡锈,道長(zhǎng)疾忍,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,836評(píng)論 1 298
  • 正文 為了忘掉前任床三,我火速辦了婚禮一罩,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘撇簿。我一直安慰自己聂渊,他們只是感情好差购,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,851評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著汉嗽,像睡著了一般欲逃。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上饼暑,一...
    開封第一講書人閱讀 52,441評(píng)論 1 310
  • 那天稳析,我揣著相機(jī)與錄音,去河邊找鬼弓叛。 笑死彰居,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的撰筷。 我是一名探鬼主播陈惰,決...
    沈念sama閱讀 40,992評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼毕籽!你這毒婦竟也來(lái)了抬闯?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,899評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤关筒,失蹤者是張志新(化名)和其女友劉穎溶握,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體平委,經(jīng)...
    沈念sama閱讀 46,457評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡奈虾,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,529評(píng)論 3 341
  • 正文 我和宋清朗相戀三年夺谁,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了廉赔。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,664評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡匾鸥,死狀恐怖蜡塌,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情勿负,我是刑警寧澤馏艾,帶...
    沈念sama閱讀 36,346評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站奴愉,受9級(jí)特大地震影響琅摩,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜锭硼,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,025評(píng)論 3 334
  • 文/蒙蒙 一房资、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧檀头,春花似錦轰异、人聲如沸岖沛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)婴削。三九已至,卻和暖如春牙肝,著一層夾襖步出監(jiān)牢的瞬間唉俗,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工惊奇, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留互躬,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,081評(píng)論 3 377
  • 正文 我出身青樓颂郎,卻偏偏與公主長(zhǎng)得像吼渡,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子乓序,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,675評(píng)論 2 359

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