探索底層原理泛粹,積累從點(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ì)象壶冒,給age
和weight
屬性賦值缕题,并且打印兩個(gè)屬性值:
通過(guò)編譯運(yùn)行,我們發(fā)現(xiàn)程序崩潰胖腾,崩潰信息就是就找不到Person
的setWeight
方法烟零。而且程序在分類中報(bào)出兩個(gè)警告,找不到Person
分類的setWeight
方法和weight
方法咸作,從而驗(yàn)證我們上面的結(jié)論锨阿。
這時(shí)可能會(huì)有人提出,既然系統(tǒng)沒(méi)有為我們生成set
记罚、get
方法的實(shí)現(xiàn)墅诡,那我們手動(dòng)實(shí)現(xiàn)set
、get
方法的實(shí)現(xiàn)不就可以了嗎桐智?我們繼續(xù)驗(yàn)證一下這個(gè)方法可不可行:
我們?cè)诜诸惖?code>.m文件中聲明全局變量_weight
末早,并實(shí)現(xiàn)set
、get
方法说庭,然后運(yùn)行程序:
通過(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ì)改變:
通過(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ò)關(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)的修飾符為:
獲得關(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ù):
函數(shù)內(nèi)部調(diào)用
_object_set_associative_reference
函數(shù)为障,我們繼續(xù)追蹤_object_set_associative_reference
函數(shù)的實(shí)現(xiàn):
通過(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ì)象:
AssociationsHashMap
我們繼續(xù)查看AssociationsHashMap
內(nèi)部的源碼鳍怨。發(fā)現(xiàn)AssociationsHashMap
繼承自unordered_map
(均由紅框標(biāo)注):
我們看到里面還包括黃框標(biāo)注的
ObjectAssociationMap
。我們先繼續(xù)查看unordered_map
內(nèi)部源碼:從unordered_map
源碼中我們可以看出_Key
和_Tp
也就是前兩個(gè)參數(shù)對(duì)應(yīng)著map中的Key
和Value
跪妥,那么對(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ù)形式傳入侦香,并以key
、Value
的方式存儲(chǔ)著ObjcAssociation
(AssociationsHashMap圖中用黃線標(biāo)注)纽疟。
我們繼續(xù)來(lái)到ObjcAssociation
源碼中查看:
我們發(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ù)中傳入的value
和policy
這兩個(gè)值最終是存儲(chǔ)在ObjcAssociation
中的散吵。
現(xiàn)在我們已經(jīng)對(duì)AssociationsManager
、 AssociationsHashMap
、 ObjectAssociationMap
错蝴、ObjcAssociation
四個(gè)對(duì)象之間的關(guān)系有了簡(jiǎn)單的認(rèn)識(shí)洲愤,那么接下來(lái)我們回到objc_setAssociatedObject
源碼,具體查看一下objc_setAssociatedObject
函數(shù)中傳入的四個(gè)參數(shù)究竟做了哪些操作:
圖中1標(biāo)注顷锰,首先根據(jù)傳入的
value
經(jīng)過(guò)acquireValue
函數(shù)處理獲取new_value
柬赐。acquireValue
函數(shù)內(nèi)部其實(shí)是通過(guò)對(duì)策略的判斷返回不同的值:圖中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)算:圖中4標(biāo)注酝陈,我們看到被處理成
new_value
的value
,和policy
一起被存入了ObjcAssociation
中毁涉。而ObjcAssociation
對(duì)應(yīng)我們傳入的key
被存入了ObjectAssociationMap
中沉帮。disguised_object
和ObjectAssociationMap
則以key-value
的形式對(duì)應(yīng)存儲(chǔ)在associations
中也就是AssociationsHashMap
中。
如果我們value
傳入nil
的話則會(huì)執(zhí)行圖中5標(biāo)注的代碼贫堰。
我們將以上分析用一張示意圖來(lái)展示:
分析到這里我們可以總結(jié)出:一個(gè)實(shí)例對(duì)象就對(duì)應(yīng)一個(gè)ObjectAssociationMap
穆壕,而ObjectAssociationMap
里面存儲(chǔ)著多個(gè)此實(shí)例對(duì)象的關(guān)聯(lián)對(duì)象的key
以及ObjcAssociation
,ObjcAssociation
中存儲(chǔ)著關(guān)聯(lián)對(duì)象的value
和policy
其屏。
我們也可以得出結(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ù):
進(jìn)入
_object_get_associative_reference
函數(shù)查看:分析源碼可知川背,先拿到
AssociationsManager
中的AssociationsHashMap
,在里面查找disguised_object
蛤袒,即我們傳入的object
參數(shù)熄云,如果存在就取出ObjectAssociationMap
,并在ObjectAssociationMap
中查找key
對(duì)應(yīng)的value
汗盘,即ObjcAssociation
皱碘,然后再ObjcAssociation
中取出value
和policy
,最后返回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)部源碼:
大概邏輯就是遍歷所有
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)歡迎大家留言。