深入學習Objective-C(二)理解 objc 關聯對象 (Associated Objects)

我們都知道,我們在普通的 objc 類中,一般我們都會把成員變量聲明在@interface中,如果你想把成員變量暴露在頭文件中,你可以把它聲明在實現文件中,甚至你也可以放在類擴展的區域中,但是,你卻不能在普通的類目中聲明成員變量。因爲普通的類目只是用來擴展方法的,不能用來擴展成員變量。

有些時候,我們在設計代碼時,會把代碼分離成很多個類目,這樣方便代碼的管理。如下所示:

對每種類型的 API 單獨放在對應的類目中,在代碼組織上就非常清晰。但是,現在有一個這樣的問題,有時候,我們需要在類目中保存一些狀態,可以使用成員變量的形式來實現,但是,這些狀態又是相對獨立的,只在這個類目中會被用到,其他的類目不會使用它。所以,我們想把這個成員變量隱藏在這個類目中,對於其他的類目,這個成員變量都不可見。

如果類目允許擴展成員變量的話,這個問題就很好解決,直接在類目的實現文件裏聲明一個成員變量即可。這樣既能對外部隱藏這個成員變量,又能在這個類目中使用它。很完美的解決方案,但是不幸的是,objc 不允許在類目中擴展成員變量。所以你不得不在類的聲明中聲明需要的成員變量,而且還需要把它暴露出來,以使你的類目能夠使用它。這樣,隨着類目越來越多,你不得不在類中聲明越來越多原本需要對外隱藏的成員變量。

我們上面遇到的問題,在一定程度上可以使用關聯對象來解決,稍後我們會看一個具體的例子。現在,我們先來看看關聯對象的基本使用方法。

我們在使用關聯對象時,主要會用到下面兩個方法:

void objc_setAssociatedObject(id object, void *key, id value, objc_AssociationPolicy policy)

id objc_getAssociatedObject(id object, void *key)

我們通過代碼來看看如何使用它:

NSURL *u1 = [NSURL URLWithString:@"blog.codingcoder.com"];
objc_setAssociatedObject(u1, @"u1key", @"s1assciated", OBJC_ASSOCIATION_COPY);

id get1 = objc_getAssociatedObject(u1, @"u1key");


NSURL *u2 = [NSURL URLWithString:@"u2"];
id get2 = objc_getAssociatedObject(u2, @"u1key");

objc_setAssociatedObject 就是把一個對象關聯到另外一個對象上去,比如下面的代碼就是把一個字符串@"XXXX"關聯到對象u1上面。這樣就類似於對象u1對象上多了一個成員變量。

objc_setAssociatedObject(u1, @"u1key", @"XXXX", OBJC_ASSOCIATION_COPY);

特別注意一點,這裏的關聯對象,是關聯到某一個具體的對象的,並不是關聯到類的。

NSURL *u2 = [NSURL URLWithString:@"u2"];
id get2 = objc_getAssociatedObject(u2, @"u1key");

上面的代碼中,u2變量並沒有關聯一個對象,所以,你不能通過objc_getAssociatedObject獲取一個值。

另外需要注意一點,對象和被關聯對象的生命週期是相互獨立的。

根據WWDC 2011, Session 322 (第36分鐘左右)發佈的內存銷燬時間表,被關聯的對象在生命週期內要比對象本身釋放的晚很多。它們會在被 NSObject -dealloc 調用的 object_dispose() 方法中釋放。

下面我們來看一個例子,相信 AFNetworking 這個類庫大家應該都很熟悉。在這個類庫中,有一個UIImageView+AFNetworking的類目。

我們看一下源碼:

我們可以看到,它生命了一個私有屬性af_imageRequestOperation,而它通過自定義 getter / setter 方法,使用objc_getAssociatedObject 和 objc_setAssociatedObject方法來達到類似與添加成員變量的目的,而這個變量對外部是完全隱藏的。所以,這也就解決了我們最開始描述的那個類目無法擴展成員變量的問題。

最後,需要注意的一點是,這種objc_setAssociatedObject的方式是運用了 objc runtime 的特性,它確實很強大,但是,相對應的,不正確的使用或者濫用 runtime 會帶來比較麻煩的 bug。所以,只有在你確定必須要使用 runtime 特性時,而且你完全明白你這麼做所帶來的影響時,才使用這種特性。一定不要爲了使用這種技術而使用它。

參考文章:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章