給分類關(guān)聯(lián)屬性(objc_setAssociatedObject、objc_getAssociatedObject黔衡、objc_removeAssociatedObjects)

關(guān)聯(lián)

關(guān)聯(lián)是指把兩個(gè)對(duì)象相互關(guān)聯(lián)起來(lái)蚓聘,使得其中的一個(gè)對(duì)象作為另外一個(gè)對(duì)象的一部分。 ? ?關(guān)聯(lián)特性只有在Mac OS X V10.6以及以后的版本上才是可用的盟劫。

在類的定義之外為類增加額外的存儲(chǔ)空間

? ? 使用關(guān)聯(lián)夜牡,我們可以不用修改類的定義而為其對(duì)象增加存儲(chǔ)空間。這在我們無(wú)法訪問到類的源碼的時(shí)候或者是考慮到二進(jìn)制兼容性的時(shí)候是非常有用侣签。 關(guān)聯(lián)是基于關(guān)鍵字的塘装,因此,我們可以為任何對(duì)象增加任意多的關(guān)聯(lián)影所,每個(gè)都使用不同的關(guān)鍵字即可蹦肴。關(guān)聯(lián)是可以保證被關(guān)聯(lián)的對(duì)象在關(guān)聯(lián)對(duì)象的整個(gè)生命周期都是可用的(在垃圾自動(dòng)回收環(huán)境下也不會(huì)導(dǎo)致資源不可回收)。

創(chuàng)建關(guān)聯(lián)

? ? 創(chuàng)建關(guān)聯(lián)要使用到Objective-C的運(yùn)行時(shí)函數(shù):objc_setAssociatedObject來(lái)把一個(gè)對(duì)象與另外一個(gè)對(duì)象進(jìn)行關(guān)聯(lián)猴娩。該函數(shù)需要四個(gè)參數(shù):源對(duì)象阴幌,關(guān)鍵字勺阐,關(guān)聯(lián)的對(duì)象和一個(gè)關(guān)聯(lián)策略。當(dāng)然矛双,此處的關(guān)鍵字和關(guān)聯(lián)策略是需要進(jìn)一步討論的渊抽。

? ■ ?關(guān)鍵字是一個(gè)void類型的指針。每一個(gè)關(guān)聯(lián)的關(guān)鍵字必須是唯一的议忽。通常都是會(huì)采用靜態(tài)變量來(lái)作為關(guān)鍵字懒闷。

? ■ ?關(guān)聯(lián)策略表明了相關(guān)的對(duì)象是通過賦值,保留引用還是復(fù)制的方式進(jìn)行關(guān)聯(lián)的徙瓶;還有這種關(guān)聯(lián)是原子的還是非原子的毛雇。這里的關(guān)聯(lián)策略和聲明屬性時(shí)的很類似。這種關(guān)聯(lián)策略是通過使用預(yù)先定義好的常量來(lái)表示的侦镇。


為何創(chuàng)建關(guān)聯(lián)

? ? 我們?cè)?iOS 開發(fā)中經(jīng)常需要使用分類(Category)灵疮,為已經(jīng)存在的類添加屬性的需求,但是使用@property并不能在分類中正確創(chuàng)建實(shí)例變量和存取方法壳繁。

不過震捣,通過 Objective-C 運(yùn)行時(shí)中的關(guān)聯(lián)對(duì)象,也就是 Associated Object闹炉,我們可以實(shí)現(xiàn)上述需求蒿赢。


關(guān)聯(lián)對(duì)象的應(yīng)用

? ? 關(guān)于關(guān)聯(lián)對(duì)象的使用相信已經(jīng)成為了一個(gè)老生常談的問題了,不過為了保證這篇文章的完整性渣触,筆者還是會(huì)在這里為各位介紹這部分的內(nèi)容的羡棵。

分類中的 @property

@property可以說(shuō)是一個(gè) Objective-C 編程中的“宏”,它有元編程的思想嗅钻。

@interfaceDKObject:NSObject@property(nonatomic,strong)NSString*property;@end

在使用上述代碼時(shí)會(huì)做三件事:

生成實(shí)例變量_property

生成getter方法- property

生成setter方法- setProperty:

@implementationDKObject{NSString*_property;}- (NSString*)property {return_property;}- (void)setProperty:(NSString*)property {? ? _property = property;}@end

這些代碼都是編譯器為我們生成的皂冰,雖然你看不到它,但是它確實(shí)在這里养篓,我們既然可以在類中使用@property生成一個(gè)屬性秃流,那么為什么在分類中不可以呢?

我們來(lái)做一個(gè)小實(shí)驗(yàn):創(chuàng)建一個(gè)DKObject的分類Category柳弄,并添加一個(gè)屬性categoryProperty:

@interfaceDKObject(Category)@property(nonatomic,strong)NSString*categoryProperty;@end

看起來(lái)還是很不錯(cuò)的舶胀,不過 Build 一下這個(gè) Demo,會(huì)發(fā)現(xiàn)有這么一個(gè)警告:

objc-ao-warning-category-property

在這里的警告告訴我們categoryProperty屬性的存取方法需要自己手動(dòng)去實(shí)現(xiàn)碧注,或者使用@dynamic在運(yùn)行時(shí)實(shí)現(xiàn)這些方法嚣伐。

換句話說(shuō),分類中的@property并沒有為我們生成實(shí)例變量以及存取方法应闯,而需要我們手動(dòng)實(shí)現(xiàn)纤控。

使用關(guān)聯(lián)對(duì)象

Q:我們?yōu)槭裁匆褂藐P(guān)聯(lián)對(duì)象?

A:因?yàn)樵诜诸愔蠤property并不會(huì)自動(dòng)生成實(shí)例變量以及存取方法碉纺,所以一般使用關(guān)聯(lián)對(duì)象為已經(jīng)存在的類添加『屬性』船万。

上一小節(jié)的內(nèi)容已經(jīng)給了我們需要使用關(guān)聯(lián)對(duì)象的理由刻撒。在這里,我們會(huì)介紹 ObjC 運(yùn)行時(shí)為我們提供的與關(guān)聯(lián)對(duì)象有關(guān)的 API耿导,并在分類中實(shí)現(xiàn)一個(gè)偽屬性

#import"DKObject+Category.h"#import<objc/runtime.h>@implementationDKObject(Category)- (NSString*)categoryProperty {returnobjc_getAssociatedObject(self, _cmd);}- (void)setCategoryProperty:(NSString*)categoryProperty {? ? objc_setAssociatedObject(self,@selector(categoryProperty), categoryProperty, OBJC_ASSOCIATION_RETAIN_NONATOMIC);}@end

這里的_cmd代指當(dāng)前方法的選擇子声怔,也就是@selector(categoryProperty)。

我們使用了兩個(gè)方法objc_getAssociatedObject以及objc_setAssociatedObject來(lái)模擬『屬性』的存取方法舱呻,而使用關(guān)聯(lián)對(duì)象模擬實(shí)例變量醋火。

在這里有必要解釋兩個(gè)問題:

為什么向方法中傳入@selector(categoryProperty)?

OBJC_ASSOCIATION_RETAIN_NONATOMIC是干什么的箱吕?

關(guān)于第一個(gè)問題芥驳,我們需要看一下這兩個(gè)方法的原型:

idobjc_getAssociatedObject(idobject,constvoid*key);voidobjc_setAssociatedObject(idobject,constvoid*key,idvalue, objc_AssociationPolicy policy);

@selector(categoryProperty)也就是參數(shù)中的key,其實(shí)可以使用靜態(tài)指針static void *類型的參數(shù)來(lái)代替茬高,不過在這里兆旬,筆者強(qiáng)烈推薦使用@selector(categoryProperty)作為key傳入。因?yàn)檫@種方法省略了聲明參數(shù)的代碼怎栽,并且能很好地保證key的唯一性丽猬。

OBJC_ASSOCIATION_RETAIN_NONATOMIC又是什么呢?如果我們使用Command加左鍵查看它的定義:

typedefOBJC_ENUM(uintptr_t, objc_AssociationPolicy) {? ? OBJC_ASSOCIATION_ASSIGN =0,/**< Specifies a weak reference to the associated object. */OBJC_ASSOCIATION_RETAIN_NONATOMIC =1,/**< Specifies a strong reference to the associated object.

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? *? The association is not made atomically. */OBJC_ASSOCIATION_COPY_NONATOMIC =3,/**< Specifies that the associated object is copied.

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? *? The association is not made atomically. */OBJC_ASSOCIATION_RETAIN =01401,/**< Specifies a strong reference to the associated object.

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? *? The association is made atomically. */OBJC_ASSOCIATION_COPY =01403/**< Specifies that the associated object is copied.

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? *? The association is made atomically. */};

從這里的注釋我們能看到很多東西熏瞄,也就是說(shuō)不同的objc_AssociationPolicy對(duì)應(yīng)了不通的屬性修飾符:

objc_AssociationPolicymodifier

OBJC_ASSOCIATION_ASSIGNassign

OBJC_ASSOCIATION_RETAIN_NONATOMICnonatomic, strong

OBJC_ASSOCIATION_COPY_NONATOMICnonatomic, copy

OBJC_ASSOCIATION_RETAINatomic, strong

OBJC_ASSOCIATION_COPYatomic, copy

而我們?cè)诖a中實(shí)現(xiàn)的屬性categoryProperty就相當(dāng)于使用了nonatomic和strong修飾符脚祟。

關(guān)于屬性修飾符的區(qū)別,并不是這篇文章的主要內(nèi)容强饮,如果你需要了解它們的區(qū)別由桌,Google是一個(gè)很好的選擇。

到這里邮丰,我們已經(jīng)完成了對(duì)關(guān)聯(lián)對(duì)象應(yīng)用的介紹沥寥,再來(lái)回顧一下小節(jié)的內(nèi)容。

@property` 其實(shí)有元編程的思想柠座,它能夠?yàn)槲覀冏詣?dòng)生成實(shí)例變量以及存取方法,而這三者構(gòu)成了屬性這個(gè)類似于語(yǔ)法糖的概念片橡,為我們提供了更便利的點(diǎn)語(yǔ)法來(lái)訪問屬性:

self.property <=> [selfproperty]self.property = value <=> [selfsetProperty:value]

在分類中妈经,因?yàn)轭惖膶?shí)例變量的布局已經(jīng)固定,使用@property已經(jīng)無(wú)法向固定的布局中添加新的實(shí)例變量(這樣做可能會(huì)覆蓋子類的實(shí)例變量)捧书,所以我們需要使用關(guān)聯(lián)對(duì)象以及兩個(gè)方法來(lái)模擬構(gòu)成屬性的三個(gè)要素吹泡。

如果你是一個(gè) iOS 開發(fā)方面的新手,我相信這篇文章的前半部分對(duì)已經(jīng)足夠使用了经瓷,不過爆哑,如果你還對(duì)關(guān)聯(lián)對(duì)象的實(shí)現(xiàn)非常感興趣,也可以嘗試閱讀下面的內(nèi)容舆吮。

關(guān)聯(lián)對(duì)象的實(shí)現(xiàn)

? ? 探索關(guān)聯(lián)對(duì)象的實(shí)現(xiàn)一直是我想要做的一件事情揭朝,直到最近队贱,我才有足夠的時(shí)間來(lái)完成這篇文章,希望能夠?qū)Ω魑蛔x者有所幫助潭袱。

這一部分會(huì)從三個(gè) objc 運(yùn)行時(shí)的方法為入口來(lái)對(duì)關(guān)聯(lián)對(duì)象的實(shí)現(xiàn)一探究竟柱嫌,其中兩個(gè)方法是上一部分使用到的方法:

voidobjc_setAssociatedObject(idobject,constvoid*key,idvalue, objc_AssociationPolicy policy);idobjc_getAssociatedObject(idobject,constvoid*key);voidobjc_removeAssociatedObjects(idobject);

三個(gè)方法的作用分別是:

以鍵值對(duì)形式添加關(guān)聯(lián)對(duì)象

根據(jù)key獲取關(guān)聯(lián)對(duì)象

移除所有關(guān)聯(lián)對(duì)象

而接下來(lái)的內(nèi)容自然就是圍繞這三個(gè)方法進(jìn)行的,我們會(huì)對(duì)它們的實(shí)現(xiàn)進(jìn)行分析屯换。

objc_setAssociatedObject

首先是objc_setAssociatedObject方法编丘,這個(gè)方法的調(diào)用棧并不復(fù)雜:

voidobjc_setAssociatedObject(idobject,constvoid*key,idvalue, objc_AssociationPolicy policy) └──voidobjc_setAssociatedObject_non_gc(idobject,constvoid*key,idvalue, objc_AssociationPolicy policy)? ? └──void_object_set_associative_reference(idobject,void*key,idvalue, uintptr_t policy)

調(diào)用棧中的_object_set_associative_reference方法實(shí)際完成了設(shè)置關(guān)聯(lián)對(duì)象的任務(wù):

void_object_set_associative_reference(idobject,void*key,idvalue, uintptr_t policy) {? ? ObjcAssociation old_association(0,nil);idnew_value = value ? acquireValue(value, policy) :nil;? ? {? ? ? ? AssociationsManager manager;? ? ? ? AssociationsHashMap &associations(manager.associations());? ? ? ? ObjectAssociationMap *refs = i->second;? ? ? ? ...? ? }if(old_association.hasValue()) ReleaseValue()(old_association);}

在這里的實(shí)現(xiàn)省略了大多的實(shí)現(xiàn)代碼,而且忽略了很多邏輯上的順序彤悔,不過不要在意這里的代碼能否執(zhí)行嘉抓。

我們需要注意其中的幾個(gè)類和數(shù)據(jù)結(jié)構(gòu),因?yàn)樵诰唧w分析這個(gè)方法的實(shí)現(xiàn)之前晕窑,我們需要了解其中它們的作用:

AssociationsManager

AssociationsHashMap

ObjcAssociationMap

ObjcAssociation

AssociationsManager

AssociationsManager在源代碼中的定義是這樣的:

classAssociationsManager {staticspinlock_t _lock;staticAssociationsHashMap *_map;public:? ? AssociationsManager()? { _lock.lock(); }? ? ~AssociationsManager()? { _lock.unlock(); }? ? ? ? AssociationsHashMap &associations() {if(_map ==NULL)? ? ? ? ? ? _map = new AssociationsHashMap();return*_map;? ? }};spinlock_t AssociationsManager::_lock;AssociationsHashMap *AssociationsManager::_map =NULL;

它維護(hù)了spinlock_t和AssociationsHashMap的單例抑片,初始化它的時(shí)候會(huì)調(diào)用lock.lock()方法,在析構(gòu)時(shí)會(huì)調(diào)用lock.unlock()幕屹,而associations方法用于取得一個(gè)全局的AssociationsHashMap單例蓝丙。

也就是說(shuō)AssociationsManager通過持有一個(gè)自旋鎖spinlock_t保證對(duì)AssociationsHashMap的操作是線程安全的,即每次只會(huì)有一個(gè)線程對(duì) AssociationsHashMap 進(jìn)行操作望拖。

如何存儲(chǔ) ObjcAssociation

ObjcAssociation就是真正的關(guān)聯(lián)對(duì)象的類渺尘,上面的所有數(shù)據(jù)結(jié)構(gòu)只是為了更好的存儲(chǔ)它。

首先说敏,AssociationsHashMap用與保存從對(duì)象的disguised_ptr_t到ObjectAssociationMap的映射:

classAssociationsHashMap : public unordered_map {public:void*operator new(size_t n) {return::malloc(n); }voidoperator delete(void*ptr) { ::free(ptr); }};

而ObjectAssociationMap則保存了從key到關(guān)聯(lián)對(duì)象ObjcAssociation的映射鸥跟,這個(gè)數(shù)據(jù)結(jié)構(gòu)保存了當(dāng)前對(duì)象對(duì)應(yīng)的所有關(guān)聯(lián)對(duì)象

classObjectAssociationMap : public std::map {public:void*operator new(size_t n) {return::malloc(n); }voidoperator delete(void*ptr) { ::free(ptr); }};

最關(guān)鍵的ObjcAssociation包含了policy以及value:

classObjcAssociation {? ? uintptr_t _policy;id_value;public:? ? ObjcAssociation(uintptr_t policy,idvalue) : _policy(policy), _value(value) {}? ? ObjcAssociation() : _policy(0), _value(nil) {}? ? uintptr_t policy()const{return_policy; }idvalue()const{return_value; }boolhasValue() {return_value !=nil; }};

舉一個(gè)簡(jiǎn)單的例子來(lái)說(shuō)明關(guān)聯(lián)對(duì)象在內(nèi)存中以什么形式存儲(chǔ)的,以下面的代碼為例:

intmain(intargc,constchar* argv[]) {@autoreleasepool{NSObject*obj = [NSObjectnew];? ? ? ? objc_setAssociatedObject(obj,@selector(hello),@"Hello", OBJC_ASSOCIATION_RETAIN_NONATOMIC);? ? }return0;}

這里的關(guān)聯(lián)對(duì)象ObjcAssociation(OBJC_ASSOCIATION_RETAIN_NONATOMIC, @"Hello")在內(nèi)存中是這么存儲(chǔ)的:

objc-ao-associateobjcect

接下來(lái)我們可以重新回到對(duì)objc_setAssociatedObject方法的分析了盔沫。

在這里會(huì)將方法的執(zhí)行分為兩種情況:

new_value != nil設(shè)置/更新關(guān)聯(lián)對(duì)象的值

new_value == nil刪除一個(gè)關(guān)聯(lián)對(duì)象

new_value != nil

先來(lái)分析在new_value != nil的情況下医咨,該方法的執(zhí)行是什么樣的:

void_object_set_associative_reference(idobject,void*key,idvalue, uintptr_t policy) {? ? ObjcAssociation old_association(0,nil);idnew_value = value ? acquireValue(value, policy) :nil;? ? {? ? ? ? AssociationsManager manager;? ? ? ? AssociationsHashMap &associations(manager.associations());? ? ? ? disguised_ptr_t disguised_object = DISGUISE(object);? ? ? ? AssociationsHashMap::iterator i = associations.find(disguised_object);if(i != associations.end()) {? ? ? ? ? ? ObjectAssociationMap *refs = i->second;? ? ? ? ? ? ObjectAssociationMap::iterator j = refs->find(key);if(j != refs->end()) {? ? ? ? ? ? ? ? old_association = j->second;? ? ? ? ? ? ? ? j->second = ObjcAssociation(policy, new_value);? ? ? ? ? ? }else{? ? ? ? ? ? ? ? (*refs)[key] = ObjcAssociation(policy, new_value);? ? ? ? ? ? }? ? ? ? }else{? ? ? ? ? ? ObjectAssociationMap *refs = new ObjectAssociationMap;? ? ? ? ? ? associations[disguised_object] = refs;? ? ? ? ? ? (*refs)[key] = ObjcAssociation(policy, new_value);? ? ? ? ? ? object->setHasAssociatedObjects();? ? ? ? }? ? }if(old_association.hasValue()) ReleaseValue()(old_association);}

使用old_association(0, nil)創(chuàng)建一個(gè)臨時(shí)的ObjcAssociation對(duì)象(用于持有原有的關(guān)聯(lián)對(duì)象,方便在方法調(diào)用的最后釋放值)

調(diào)用acquireValue對(duì)new_value進(jìn)行retain或者copy

staticidacquireValue(idvalue, uintptr_t policy) {switch(policy &0xFF) {caseOBJC_ASSOCIATION_SETTER_RETAIN:return((id(*)(id, SEL))objc_msgSend)(value, SEL_retain);caseOBJC_ASSOCIATION_SETTER_COPY:return((id(*)(id, SEL))objc_msgSend)(value, SEL_copy);? ? }returnvalue;}

初始化一個(gè)AssociationsManager架诞,并獲取唯一的保存關(guān)聯(lián)對(duì)象的哈希表AssociationsHashMap

AssociationsManager manager;

AssociationsHashMap &associations(manager.associations());

先使用DISGUISE(object)作為 key 尋找對(duì)應(yīng)的ObjectAssociationMap

如果沒有找到拟淮,初始化一個(gè)ObjectAssociationMap,再實(shí)例化ObjcAssociation對(duì)象添加到 Map 中谴忧,并調(diào)用setHasAssociatedObjects方法很泊,表明當(dāng)前對(duì)象含有關(guān)聯(lián)對(duì)象

ObjectAssociationMap *refs = new ObjectAssociationMap;

associations[disguised_object] = refs;

(*refs)[key] = ObjcAssociation(policy, new_value);

object->setHasAssociatedObjects();

如果找到了對(duì)應(yīng)的ObjectAssociationMap,就要看key是否存在了沾谓,由此來(lái)決定是更新原有的關(guān)聯(lián)對(duì)象委造,還是增加一個(gè)

ObjectAssociationMap *refs = i->second;ObjectAssociationMap::iterator j = refs->find(key);if(j != refs->end()) {? ? old_association = j->second;? ? j->second = ObjcAssociation(policy, new_value);}else{? ? (*refs)[key] = ObjcAssociation(policy, new_value);}

最后的最后,如果原來(lái)的關(guān)聯(lián)對(duì)象有值的話均驶,會(huì)調(diào)用ReleaseValue()釋放關(guān)聯(lián)對(duì)象的值

structReleaseValue {voidoperator() (ObjcAssociation &association) {? ? ? ? releaseValue(association.value(), association.policy());? ? }};staticvoidreleaseValue(idvalue, uintptr_t policy) {if(policy & OBJC_ASSOCIATION_SETTER_RETAIN) {? ? ? ? ((id(*)(id, SEL))objc_msgSend)(value, SEL_release);? ? }}

到這里昏兆,該條件下的方法實(shí)現(xiàn)就結(jié)束了。

new_value == nil

如果new_value == nil妇穴,就說(shuō)明我們要?jiǎng)h除對(duì)應(yīng)key的關(guān)聯(lián)對(duì)象爬虱,實(shí)現(xiàn)如下:

void_object_set_associative_reference(idobject,void*key,idvalue, uintptr_t policy) {? ? ObjcAssociation old_association(0,nil);idnew_value = value ? acquireValue(value, policy) :nil;? ? {? ? ? ? AssociationsManager manager;? ? ? ? AssociationsHashMap &associations(manager.associations());? ? ? ? disguised_ptr_t disguised_object = DISGUISE(object);? ? ? ? AssociationsHashMap::iterator i = associations.find(disguised_object);if(i !=? associations.end()) {? ? ? ? ? ? ObjectAssociationMap *refs = i->second;? ? ? ? ? ? ObjectAssociationMap::iterator j = refs->find(key);if(j != refs->end()) {? ? ? ? ? ? ? ? old_association = j->second;? ? ? ? ? ? ? ? refs->erase(j);? ? ? ? ? ? }? ? ? ? }? ? }if(old_association.hasValue()) ReleaseValue()(old_association);}

這種情況下方法的實(shí)現(xiàn)與前面的唯一區(qū)別就是隶债,我們會(huì)調(diào)用erase方法,擦除ObjectAssociationMap中key對(duì)應(yīng)的節(jié)點(diǎn)饮潦。

setHasAssociatedObjects()

其實(shí)上面的兩種情況已經(jīng)將objc_setAssociatedObject方法的實(shí)現(xiàn)分析得很透徹了燃异,不過,這里還有一個(gè)小問題來(lái)等待我們解決继蜡,setHasAssociatedObjects()方法的作用是什么回俐?

inlinevoidobjc_object::setHasAssociatedObjects() {if(isTaggedPointer())return; retry:? ? isa_t oldisa = LoadExclusive(&isa.bits);? ? isa_t newisa = oldisa;if(!newisa.indexed)return;if(newisa.has_assoc)return;? ? newisa.has_assoc =true;if(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits))gotoretry;}

它會(huì)將isa結(jié)構(gòu)體中的標(biāo)記位has_assoc標(biāo)記為true,也就是表示當(dāng)前對(duì)象有關(guān)聯(lián)對(duì)象稀并,在這里我還想祭出這張圖來(lái)介紹isa中的各個(gè)標(biāo)記位都是干什么的仅颇。

objc-ao-isa-struct

如果想要了解關(guān)于 isa 的知識(shí),可以閱讀從 NSObject 的初始化了解 isa

objc_getAssociatedObject

我們既然已經(jīng)對(duì)objc_setAssociatedObject的實(shí)現(xiàn)已經(jīng)比較熟悉了碘举,相信對(duì)于objc_getAssociatedObject的理解也會(huì)更加容易忘瓦。

方法的調(diào)用棧和objc_setAssociatedObject非常相似:

idobjc_getAssociatedObject(idobject,constvoid*key)└──idobjc_getAssociatedObject_non_gc(idobject,constvoid*key);? ? └──id_object_get_associative_reference(idobject,void*key)

而_object_get_associative_reference相比于前面方法的實(shí)現(xiàn)更加簡(jiǎn)單。

id_object_get_associative_reference(idobject,void*key) {idvalue =nil;? ? uintptr_t policy = OBJC_ASSOCIATION_ASSIGN;? ? {? ? ? ? AssociationsManager manager;? ? ? ? AssociationsHashMap &associations(manager.associations());? ? ? ? disguised_ptr_t disguised_object = DISGUISE(object);? ? ? ? AssociationsHashMap::iterator i = associations.find(disguised_object);if(i != associations.end()) {? ? ? ? ? ? ObjectAssociationMap *refs = i->second;? ? ? ? ? ? ObjectAssociationMap::iterator j = refs->find(key);if(j != refs->end()) {? ? ? ? ? ? ? ? ObjcAssociation &entry = j->second;? ? ? ? ? ? ? ? value = entry.value();? ? ? ? ? ? ? ? policy = entry.policy();if(policy & OBJC_ASSOCIATION_GETTER_RETAIN) ((id(*)(id, SEL))objc_msgSend)(value, SEL_retain);? ? ? ? ? ? }? ? ? ? }? ? }if(value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {? ? ? ? ((id(*)(id, SEL))objc_msgSend)(value, SEL_autorelease);? ? }returnvalue;}

代碼中尋找關(guān)聯(lián)對(duì)象的邏輯和objc_setAssociatedObject差不多:

獲取靜態(tài)變量AssociationsHashMap

以DISGUISE(object)為 key 查找AssociationsHashMap

以void *key為 key 查找ObjcAssociation

根據(jù)policy調(diào)用相應(yīng)的方法

if(policy & OBJC_ASSOCIATION_GETTER_RETAIN) ((id(*)(id, SEL))objc_msgSend)(value, SEL_retain);if(value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {? ? ((id(*)(id, SEL))objc_msgSend)(value, SEL_autorelease);}

返回關(guān)聯(lián)對(duì)象ObjcAssociation的值

objc_removeAssociatedObjects

關(guān)于最后的objc_removeAssociatedObjects方法引颈,其實(shí)現(xiàn)也相對(duì)簡(jiǎn)單耕皮,這是方法的調(diào)用棧:

voidobjc_removeAssociatedObjects(idobject)└──void_object_remove_assocations(idobject)

這是簡(jiǎn)化版本的objc_removeAssociatedObjects方法實(shí)現(xiàn):

voidobjc_removeAssociatedObjects(idobject) {if(object && object->hasAssociatedObjects()) {? ? ? ? _object_remove_assocations(object);? ? }}

為了加速移除對(duì)象的關(guān)聯(lián)對(duì)象的速度,我們會(huì)通過標(biāo)記位has_assoc來(lái)避免不必要的方法調(diào)用蝙场,在確認(rèn)了對(duì)象和關(guān)聯(lián)對(duì)象的存在之后凌停,才會(huì)調(diào)用_object_remove_assocations方法移除對(duì)象上所有的關(guān)聯(lián)對(duì)象:

void_object_remove_assocations(idobject) {? ? vector< ObjcAssociation,ObjcAllocator > elements;? ? {? ? ? ? AssociationsManager manager;? ? ? ? AssociationsHashMap &associations(manager.associations());if(associations.size() ==0)return;? ? ? ? disguised_ptr_t disguised_object = DISGUISE(object);? ? ? ? AssociationsHashMap::iterator i = associations.find(disguised_object);if(i != associations.end()) {? ? ? ? ? ? ObjectAssociationMap *refs = i->second;for(ObjectAssociationMap::iterator j = refs->begin(), end = refs->end(); j != end; ++j) {? ? ? ? ? ? ? ? elements.push_back(j->second);? ? ? ? ? ? }? ? ? ? ? ? delete refs;? ? ? ? ? ? associations.erase(i);? ? ? ? }? ? }? ? for_each(elements.begin(), elements.end(), ReleaseValue());}

方法會(huì)將對(duì)象包含的所有關(guān)聯(lián)對(duì)象加入到一個(gè)vector中,然后對(duì)所有的ObjcAssociation對(duì)象調(diào)用ReleaseValue()方法售滤,釋放不再被需要的值罚拟。

小結(jié)

關(guān)于應(yīng)用

本來(lái)在這個(gè)系列的文章中并不會(huì)涉及關(guān)聯(lián)對(duì)象這個(gè)話題,不過完箩,有人問過我這么一個(gè)問題:在分類中到底能否實(shí)現(xiàn)屬性赐俗?其實(shí)在回答這個(gè)問題之前,首先要知道到底屬性是什么弊知?而屬性的概念決定了這個(gè)問題的答案阻逮。

如果你把屬性理解為通過方法訪問的實(shí)例變量,我相信這個(gè)問題的答案是不能秩彤,因?yàn)榉诸惒荒転轭愒黾宇~外的實(shí)例變量夺鲜。

不過如果屬性只是一個(gè)存取方法以及存儲(chǔ)值的容器的集合,那么分類是可以實(shí)現(xiàn)屬性的呐舔。

分類中對(duì)屬性的實(shí)現(xiàn)其實(shí)只是實(shí)現(xiàn)了一個(gè)看起來(lái)像屬性的接口而已

關(guān)于實(shí)現(xiàn)

關(guān)聯(lián)對(duì)象又是如何實(shí)現(xiàn)并且管理的呢:

關(guān)聯(lián)對(duì)象其實(shí)就是ObjcAssociation對(duì)象

關(guān)聯(lián)對(duì)象由AssociationsManager管理并在AssociationsHashMap存儲(chǔ)

對(duì)象的指針以及其對(duì)應(yīng)ObjectAssociationMap以鍵值對(duì)的形式存儲(chǔ)在AssociationsHashMap中

ObjectAssociationMap則是用于存儲(chǔ)關(guān)聯(lián)對(duì)象的數(shù)據(jù)結(jié)構(gòu)

每一個(gè)對(duì)象都有一個(gè)標(biāo)記位has_assoc指示對(duì)象是否含有關(guān)聯(lián)對(duì)象


鏈接:http://www.reibang.com/p/79479a09a8c0#%E5%85%B3%E8%81%94%E5%AF%B9%E8%B1%A1%E7%9A%84%E5%BA%94%E7%94%A8

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末慷蠕,一起剝皮案震驚了整個(gè)濱河市珊拼,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌流炕,老刑警劉巖澎现,帶你破解...
    沈念sama閱讀 216,591評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件仅胞,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡剑辫,警方通過查閱死者的電腦和手機(jī)干旧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)妹蔽,“玉大人椎眯,你說(shuō)我怎么就攤上這事「炱瘢” “怎么了编整?”我有些...
    開封第一講書人閱讀 162,823評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)乳丰。 經(jīng)常有香客問我掌测,道長(zhǎng),這世上最難降的妖魔是什么产园? 我笑而不...
    開封第一講書人閱讀 58,204評(píng)論 1 292
  • 正文 為了忘掉前任汞斧,我火速辦了婚禮,結(jié)果婚禮上什燕,老公的妹妹穿的比我還像新娘粘勒。我一直安慰自己,他們只是感情好秋冰,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,228評(píng)論 6 388
  • 文/花漫 我一把揭開白布仲义。 她就那樣靜靜地躺著,像睡著了一般剑勾。 火紅的嫁衣襯著肌膚如雪埃撵。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,190評(píng)論 1 299
  • 那天虽另,我揣著相機(jī)與錄音暂刘,去河邊找鬼。 笑死捂刺,一個(gè)胖子當(dāng)著我的面吹牛谣拣,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播族展,決...
    沈念sama閱讀 40,078評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼森缠,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了仪缸?” 一聲冷哼從身側(cè)響起贵涵,我...
    開封第一講書人閱讀 38,923評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后宾茂,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體瓷马,經(jīng)...
    沈念sama閱讀 45,334評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,550評(píng)論 2 333
  • 正文 我和宋清朗相戀三年跨晴,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了欧聘。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,727評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡端盆,死狀恐怖怀骤,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情爱谁,我是刑警寧澤晒喷,帶...
    沈念sama閱讀 35,428評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站访敌,受9級(jí)特大地震影響凉敲,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜寺旺,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,022評(píng)論 3 326
  • 文/蒙蒙 一爷抓、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧阻塑,春花似錦蓝撇、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至走搁,卻和暖如春独柑,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背私植。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工忌栅, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人曲稼。 一個(gè)月前我還...
    沈念sama閱讀 47,734評(píng)論 2 368
  • 正文 我出身青樓索绪,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親贫悄。 傳聞我的和親對(duì)象是個(gè)殘疾皇子瑞驱,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,619評(píng)論 2 354

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