iOS底層原理探索—關(guān)聯(lián)對(duì)象的本質(zhì)

探索底層原理泛粹,積累從點(diǎn)滴做起。大家好,我是Mars。

往期回顧

iOS底層原理探索—OC對(duì)象的本質(zhì)
iOS底層原理探索—class的本質(zhì)
iOS底層原理探索—KVO的本質(zhì)
iOS底層原理探索— KVC的本質(zhì)
iOS底層原理探索— Category的本質(zhì)(一)
iOS底層原理探索— Category的本質(zhì)(二)

今天繼續(xù)帶領(lǐng)大家探索iOS之關(guān)聯(lián)對(duì)象的本質(zhì)碌燕。

我們?cè)?a href="http://www.reibang.com/p/6735b7ec0fe5" target="_blank">iOS底層原理探索— Category的本質(zhì)(一)中提到過(guò)在iOS中分類不能直接添加成員變量。在分類中添加的屬性并不會(huì)幫助我們自動(dòng)生成成員變量瓣蛀,只會(huì)生成set陆蟆、get方法的聲明,需要我們自己去實(shí)現(xiàn)惋增。叠殷,下面我們驗(yàn)證一下:

我們創(chuàng)建一個(gè)本類Person,在類中聲明int類型的age屬性诈皿,然后創(chuàng)建一個(gè)Person的分類林束,并在分類中聲明int類型的weight屬性像棘,然后在main.m文件中導(dǎo)入分類的頭文件,創(chuàng)建一個(gè)Person的對(duì)象壶冒,給ageweight屬性賦值缕题,并且打印兩個(gè)屬性值:

測(cè)試代碼.png

通過(guò)編譯運(yùn)行,我們發(fā)現(xiàn)程序崩潰胖腾,崩潰信息就是就找不到PersonsetWeight方法烟零。而且程序在分類中報(bào)出兩個(gè)警告,找不到Person分類的setWeight方法和weight方法咸作,從而驗(yàn)證我們上面的結(jié)論锨阿。

分類屬性崩潰信息.png

分類聲明屬性警告.png

這時(shí)可能會(huì)有人提出,既然系統(tǒng)沒(méi)有為我們生成set记罚、get方法的實(shí)現(xiàn)墅诡,那我們手動(dòng)實(shí)現(xiàn)setget方法的實(shí)現(xiàn)不就可以了嗎桐智?我們繼續(xù)驗(yàn)證一下這個(gè)方法可不可行:

我們?cè)诜诸惖?code>.m文件中聲明全局變量_weight末早,并實(shí)現(xiàn)setget方法说庭,然后運(yùn)行程序:

手動(dòng)實(shí)現(xiàn)set方法.png

通過(guò)運(yùn)行我們發(fā)現(xiàn)確實(shí)完成了賦值并打印出結(jié)果然磷,但是這樣有一個(gè)問(wèn)題不知道大家有沒(méi)有發(fā)現(xiàn),我們聲明的全局變量_weight口渔,無(wú)論對(duì)象創(chuàng)建與銷毀样屠,只要程序在運(yùn)行_weight變量就存在。如果我們?cè)賱?chuàng)建一個(gè)新的Person對(duì)象缺脉,給weight賦不同的值痪欲,那么之前我們創(chuàng)建的person對(duì)象的weight屬性的值也會(huì)改變:

聲明全局變量后測(cè)試.png

通過(guò)打印看到,我們新創(chuàng)建一個(gè)Person對(duì)象student攻礼,賦值后打印之前創(chuàng)建的person對(duì)象屬性值發(fā)現(xiàn)业踢,person對(duì)象的weight屬性的值已經(jīng)改變了。

手動(dòng)添加set礁扮、get方法實(shí)現(xiàn)的辦法失敗了知举,那我們可不可以通過(guò)runtime的一些方式間接實(shí)現(xiàn)添加成員變量的效果呢?答案是可以的太伊,下面就給大家介紹關(guān)聯(lián)對(duì)象的方法雇锡。

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

在OC中,runtime提供了動(dòng)態(tài)添加屬性和獲得屬性的API:objc_setAssociatedObject僚焦、objc_getAssociatedObject锰提,下面我們用代碼來(lái)演示一下具體使用方法:

關(guān)聯(lián)對(duì)象語(yǔ)法測(cè)試.png

通過(guò)關(guān)聯(lián)對(duì)象我們很容易就實(shí)現(xiàn)了給分類添加成員變量,我們來(lái)分析一下具體的使用。

添加關(guān)聯(lián)對(duì)象

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

/*  參數(shù)解讀
* object : 給哪個(gè)對(duì)象添加屬性立肘。這里要給自己添加屬性边坤,用self。
* key : 屬性名谅年,根據(jù)key獲取關(guān)聯(lián)對(duì)象的屬性的值茧痒。傳入一個(gè)指針即可。在objc_getAssociatedObject中通過(guò)次key獲得屬性的值并返回融蹂。
* value : 關(guān)聯(lián)的值旺订,也就是通過(guò)set方法傳入的值給屬性。
* policy : 策略殿较,屬性以什么形式保存耸峭。
*/

其中參數(shù)policy對(duì)應(yīng)的是枚舉值:

typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
    // 指定一個(gè)弱引用相關(guān)聯(lián)的對(duì)象    
    OBJC_ASSOCIATION_ASSIGN = 0,      
    // 指定相關(guān)對(duì)象的強(qiáng)引用桩蓉,非原子性
    OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, 
    // 指定相關(guān)的對(duì)象被復(fù)制淋纲,非原子性    
    OBJC_ASSOCIATION_COPY_NONATOMIC = 3,  
    // 指定相關(guān)對(duì)象的強(qiáng)引用,原子性    
    OBJC_ASSOCIATION_RETAIN = 01401,  
    // 指定相關(guān)的對(duì)象被復(fù)制院究,原子性       
    OBJC_ASSOCIATION_COPY = 01403     
};

分別對(duì)應(yīng)的修飾符為:


objc_AssociationPolicy.png

獲得關(guān)聯(lián)對(duì)象

objc_getAssociatedObject(id object, const void *key);
/*  參數(shù)解讀
* object : 獲取哪個(gè)對(duì)象里面的關(guān)聯(lián)的屬性洽瞬。
* key : 屬性,與objc_setAssociatedObject中的key相對(duì)應(yīng)业汰,即通過(guò)key值取出value伙窃。

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

- (void)removeAssociatedObjects
{
   // 移除所有關(guān)聯(lián)對(duì)象
   objc_removeAssociatedObjects(self);
}

至此我們就了解了關(guān)聯(lián)對(duì)象以及如何使用關(guān)聯(lián)對(duì)象。下面我們從底層源碼來(lái)進(jìn)一步了解關(guān)聯(lián)對(duì)象的實(shí)現(xiàn)原理样漆。

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

添加關(guān)聯(lián)對(duì)象

我們?cè)?code>runtime源碼中找到objc_setAssociatedObject函數(shù):

objc_setAssociatedObject.png

函數(shù)內(nèi)部調(diào)用_object_set_associative_reference函數(shù)为障,我們繼續(xù)追蹤_object_set_associative_reference函數(shù)的實(shí)現(xiàn):

_object_set_associative_reference.png

通過(guò)紅框標(biāo)注我們找到了4個(gè)對(duì)象,其實(shí)這4個(gè)對(duì)象就是實(shí)現(xiàn)關(guān)聯(lián)對(duì)象技術(shù)的核心對(duì)象:
AssociationsManager
AssociationsHashMap
ObjectAssociationMap
ObjcAssociation

我們分別進(jìn)入這4個(gè)對(duì)象內(nèi)部查看它們之間的關(guān)系:

AssociationsManager

通過(guò)AssociationsManager內(nèi)部源碼發(fā)現(xiàn)放祟,其內(nèi)部有一個(gè)AssociationsHashMap對(duì)象:

AssociationsManager.png

AssociationsHashMap

我們繼續(xù)查看AssociationsHashMap內(nèi)部的源碼鳍怨。發(fā)現(xiàn)AssociationsHashMap繼承自unordered_map(均由紅框標(biāo)注):

AssociationsHashMap.png

我們看到里面還包括黃框標(biāo)注的ObjectAssociationMap。我們先繼續(xù)查看unordered_map內(nèi)部源碼:
unordered_map內(nèi)源碼.png

unordered_map源碼中我們可以看出_Key_Tp也就是前兩個(gè)參數(shù)對(duì)應(yīng)著map中的KeyValue跪妥,那么對(duì)照上面AssociationsHashMap內(nèi)源碼發(fā)現(xiàn)_Key中傳入的是disguised_ptr_t鞋喇,_Tp傳入的值則為ObjectAssociationMap*(AssociationsHashMap圖中用紅線標(biāo)注)。

同樣我們來(lái)到黃框標(biāo)注ObjectAssociationMap中眉撵,發(fā)現(xiàn)ObjectAssociationMap中同樣也是獎(jiǎng)ObjcAssociation以參數(shù)形式傳入侦香,并以keyValue的方式存儲(chǔ)著ObjcAssociation(AssociationsHashMap圖中用黃線標(biāo)注)纽疟。

我們繼續(xù)來(lái)到ObjcAssociation源碼中查看:

ObjcAssociation.png

我們發(fā)現(xiàn)ObjcAssociation存儲(chǔ)著_policy罐韩、_value,而這兩個(gè)值我們發(fā)現(xiàn)正是我們調(diào)用添加關(guān)聯(lián)對(duì)象objc_setAssociatedObject函數(shù)傳入的值污朽。也就是說(shuō)我們?cè)谡{(diào)用objc_setAssociatedObject函數(shù)中傳入的valuepolicy這兩個(gè)值最終是存儲(chǔ)在ObjcAssociation中的散吵。

現(xiàn)在我們已經(jīng)對(duì)AssociationsManagerAssociationsHashMapObjectAssociationMap错蝴、ObjcAssociation四個(gè)對(duì)象之間的關(guān)系有了簡(jiǎn)單的認(rèn)識(shí)洲愤,那么接下來(lái)我們回到objc_setAssociatedObject源碼,具體查看一下objc_setAssociatedObject函數(shù)中傳入的四個(gè)參數(shù)究竟做了哪些操作:

執(zhí)行順序.png

圖中1標(biāo)注顷锰,首先根據(jù)傳入的value經(jīng)過(guò)acquireValue函數(shù)處理獲取new_value柬赐。acquireValue函數(shù)內(nèi)部其實(shí)是通過(guò)對(duì)策略的判斷返回不同的值:
acquireValue.png

圖中2標(biāo)注,創(chuàng)建AssociationsManager類型的manager,拿到manager內(nèi)部的AssociationsHashMap官紫,即associations肛宋。
圖中3標(biāo)注,傳入的object參數(shù)經(jīng)過(guò)DISGUISE函數(shù)被轉(zhuǎn)化為了disguised_ptr_t類型的disguised_object束世。
其實(shí)DISGUISE函數(shù)內(nèi)部只是對(duì)object做了位運(yùn)算:
DISGUISE.png

圖中4標(biāo)注酝陈,我們看到被處理成new_valuevalue,和policy一起被存入了ObjcAssociation中毁涉。而ObjcAssociation對(duì)應(yīng)我們傳入的key被存入了ObjectAssociationMap中沉帮。disguised_objectObjectAssociationMap則以key-value的形式對(duì)應(yīng)存儲(chǔ)在associations中也就是AssociationsHashMap中。

如果我們value傳入nil的話則會(huì)執(zhí)行圖中5標(biāo)注的代碼贫堰。

我們將以上分析用一張示意圖來(lái)展示:


objc_setAssociatedObject示意圖.png

分析到這里我們可以總結(jié)出:一個(gè)實(shí)例對(duì)象就對(duì)應(yīng)一個(gè)ObjectAssociationMap穆壕,而ObjectAssociationMap里面存儲(chǔ)著多個(gè)此實(shí)例對(duì)象的關(guān)聯(lián)對(duì)象的key以及ObjcAssociationObjcAssociation中存儲(chǔ)著關(guān)聯(lián)對(duì)象的valuepolicy其屏。
我們也可以得出結(jié)論:關(guān)聯(lián)對(duì)象并不是放在了原來(lái)的對(duì)象里面喇勋,而是自己維護(hù)了一個(gè)全局的map用來(lái)存放每一個(gè)對(duì)象及其對(duì)應(yīng)關(guān)聯(lián)屬性

獲得關(guān)聯(lián)對(duì)象

我們進(jìn)入objc_getAssociatedObject函數(shù)內(nèi)部查看偎行,發(fā)現(xiàn)其內(nèi)部調(diào)用的是_object_get_associative_reference函數(shù):

objc_getAssociatedObject.png

進(jìn)入_object_get_associative_reference函數(shù)查看:
_object_get_associative_reference.png

分析源碼可知川背,先拿到AssociationsManager中的AssociationsHashMap,在里面查找disguised_object蛤袒,即我們傳入的object參數(shù)熄云,如果存在就取出ObjectAssociationMap,并在ObjectAssociationMap中查找key對(duì)應(yīng)的value汗盘,即ObjcAssociation皱碘,然后再ObjcAssociation中取出valuepolicy,最后返回value隐孽。

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

通過(guò)調(diào)用objc_removeAssociatedObjects用來(lái)刪除所有的關(guān)聯(lián)對(duì)象癌椿。其內(nèi)部調(diào)用的是_object_remove_assocations函數(shù)。我們直接查看_object_remove_assocations函數(shù)內(nèi)部源碼:

_object_remove_assocations.png

大概邏輯就是遍歷所有ObjectAssociationMap菱阵,然后進(jìn)行delete刪除操作踢俄。

當(dāng)然,即使我們沒(méi)有調(diào)用移除關(guān)聯(lián)對(duì)象的方法晴及,在對(duì)象銷毀的時(shí)候都办,對(duì)應(yīng)的關(guān)聯(lián)對(duì)象也會(huì)被銷毀。因?yàn)樵?code>dealloc執(zhí)行的時(shí)候,底層會(huì)檢查是否有關(guān)聯(lián)對(duì)象琳钉。

至此我們就完成了關(guān)聯(lián)對(duì)象的底層探索势木,如有疑問(wèn)歡迎大家留言。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末歌懒,一起剝皮案震驚了整個(gè)濱河市啦桌,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌及皂,老刑警劉巖甫男,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異验烧,居然都是意外死亡板驳,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門碍拆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)若治,“玉大人,你說(shuō)我怎么就攤上這事倔监≈鄙埃” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵浩习,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我济丘,道長(zhǎng)谱秽,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任摹迷,我火速辦了婚禮疟赊,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘峡碉。我一直安慰自己近哟,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開白布鲫寄。 她就那樣靜靜地躺著吉执,像睡著了一般。 火紅的嫁衣襯著肌膚如雪地来。 梳的紋絲不亂的頭發(fā)上戳玫,一...
    開封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音未斑,去河邊找鬼咕宿。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的府阀。 我是一名探鬼主播缆镣,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼试浙!你這毒婦竟也來(lái)了费就?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤川队,失蹤者是張志新(化名)和其女友劉穎力细,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體固额,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡眠蚂,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了斗躏。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片逝慧。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖啄糙,靈堂內(nèi)的尸體忽然破棺而出笛臣,到底是詐尸還是另有隱情,我是刑警寧澤隧饼,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布沈堡,位于F島的核電站,受9級(jí)特大地震影響燕雁,放射性物質(zhì)發(fā)生泄漏诞丽。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一拐格、第九天 我趴在偏房一處隱蔽的房頂上張望僧免。 院中可真熱鬧,春花似錦捏浊、人聲如沸懂衩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)浊洞。三九已至,卻和暖如春热康,著一層夾襖步出監(jiān)牢的瞬間沛申,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工姐军, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留铁材,地道東北人尖淘。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像著觉,于是被迫代替她去往敵國(guó)和親村生。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

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