由 王巍 (@ONEVCAT) 發(fā)布于 2016-04-27
不知道是從什么時(shí)候開(kāi)始,“是否能通過(guò) Category 給已有的類(lèi)添加成員變量” 就成為了一道 Objective-C 面試中的常見(jiàn)題目。不幸的消息是這個(gè)面試題目在 Swift 中可能依舊會(huì)存在。
得益于 Objective-C 的運(yùn)行時(shí)和 Key-Value Coding 的特性,我們可以在運(yùn)行時(shí)向一個(gè)對(duì)象添加值存儲(chǔ)。而在使用 Category 擴(kuò)展現(xiàn)有的類(lèi)的功能的時(shí)候,直接添加實(shí)例變量這種行為是不被允許的,這時(shí)候一般就使用 property 配合 Associated Object 的方式,將一個(gè)對(duì)象 “關(guān)聯(lián)” 到已有的要擴(kuò)展的對(duì)象上。進(jìn)行關(guān)聯(lián)后,在對(duì)這個(gè)目標(biāo)對(duì)象訪問(wèn)的時(shí)候,從外界看來(lái),就似乎是直接在通過(guò)屬性訪問(wèn)對(duì)象的實(shí)例變量一樣,可以非常方便。
在 Swift 中這樣的方法依舊有效,只不過(guò)在寫(xiě)法上可能有些不同。兩個(gè)對(duì)應(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 的安全性,在類(lèi)型檢查上嚴(yán)格了不少,因此我們有必要也進(jìn)行一些調(diào)整。在 Swift 中向某個(gè) extension
里使用 Associated Object 的方式將對(duì)象進(jìn)行關(guān)聯(lián)的寫(xiě)法是:
// 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) } }}// 測(cè)試func printTitle(input: MyClass) { if let title = input.title { print("Title: (title)") } else { print("沒(méi)有設(shè)置") }}let a = MyClass()printTitle(a)a.title = "Swifter.tips"printTitle(a)// 輸出:// 沒(méi)有設(shè)置// Title: Swifter.tips
key
的類(lèi)型在這里聲明為了 Void?
,并且通過(guò) &
操作符取地址并作為 UnsafePointer<Void>
類(lèi)型被傳入。這在 Swift 與 C 協(xié)作和指針操作時(shí)是一種很常見(jiàn)的用法。關(guān)于 C 的指針操作和這些 unsafe
開(kāi)頭的類(lèi)型的用法,可以參看 UnsafePointer 一節(jié)的內(nèi)容。