由 王巍 (@ONEVCAT) 發(fā)布于 2016-04-27
不知道是從什么時(shí)候開始悬包,“是否能通過 Category 給已有的類添加成員變量” 就成為了一道 Objective-C 面試中的常見題目构舟。不幸的消息是這個(gè)面試題目在 Swift 中可能依舊會(huì)存在。
得益于 Objective-C 的運(yùn)行時(shí)和 Key-Value Coding 的特性递览,我們可以在運(yùn)行時(shí)向一個(gè)對象添加值存儲。而在使用 Category 擴(kuò)展現(xiàn)有的類的功能的時(shí)候瞳腌,直接添加實(shí)例變量這種行為是不被允許的绞铃,這時(shí)候一般就使用 property 配合 Associated Object 的方式,將一個(gè)對象 “關(guān)聯(lián)” 到已有的要擴(kuò)展的對象上嫂侍。進(jìn)行關(guān)聯(lián)后儿捧,在對這個(gè)目標(biāo)對象訪問的時(shí)候,從外界看來挑宠,就似乎是直接在通過屬性訪問對象的實(shí)例變量一樣菲盾,可以非常方便。
在 Swift 中這樣的方法依舊有效各淀,只不過在寫法上可能有些不同懒鉴。兩個(gè)對應(yīng)的運(yùn)行時(shí)的 get 和 set Associated Object 的 API 是這樣的:
func objc_getAssociatedObject(object: AnyObject!, key: UnsafePointer<Void> ) -> AnyObject!func objc_setAssociatedObject(object: AnyObject!, key: UnsafePointer<Void>, value: AnyObject!, policy: objc_AssociationPolicy)
這兩個(gè) API 所接受的參數(shù)也都 Swift 化了,并且因?yàn)?Swift 的安全性碎浇,在類型檢查上嚴(yán)格了不少临谱,因此我們有必要也進(jìn)行一些調(diào)整。在 Swift 中向某個(gè) extension
里使用 Associated Object 的方式將對象進(jìn)行關(guān)聯(lián)的寫法是:
// MyClass.swiftclass MyClass {}// MyClassExtension.swiftprivate var key: Void?extension MyClass { var title: String? { get { return objc_getAssociatedObject(self, &key) as? String } set { objc_setAssociatedObject(self, &key, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } }}// 測試func printTitle(input: MyClass) { if let title = input.title { print("Title: (title)") } else { print("沒有設(shè)置") }}let a = MyClass()printTitle(a)a.title = "Swifter.tips"printTitle(a)// 輸出:// 沒有設(shè)置// Title: Swifter.tips
key
的類型在這里聲明為了 Void?
奴璃,并且通過 &
操作符取地址并作為 UnsafePointer<Void>
類型被傳入悉默。這在 Swift 與 C 協(xié)作和指針操作時(shí)是一種很常見的用法。關(guān)于 C 的指針操作和這些 unsafe
開頭的類型的用法苟穆,可以參看 UnsafePointer 一節(jié)的內(nèi)容抄课。