關(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以及以后的版本上才是可用的盟劫。
? ? 使用關(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)要使用到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)表示的侦镇。
? ? 我們?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)纤控。
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