前言
之前我們聊過了,在Category中聲明一個屬性,可以自己手動實現(xiàn)set和get方法,但是因為沒有成員變量拴袭,所以說并不能儲值。
我們可以通過runtime的api實現(xiàn)讓成員變量可以儲值曙博,其實本質(zhì)也并不是儲存拥刻,而是通過關(guān)聯(lián)對象實現(xiàn)了這種看似是可以儲值的效果。
我們可以用下面的方法來設(shè)置關(guān)聯(lián)對象父泳。
objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key, id _Nullable value, objc_AssociationPolicy policy)
objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key);
今天我們就來探究一下這種關(guān)聯(lián)對象的實現(xiàn)邏輯般哼。
實現(xiàn)
我們在runtime源碼中搜索objc_setAssociatedObject
吴汪,最終可以定位到下面這個方法。
_object_set_associative_reference
通過簡單的分析源碼蒸眠,我們可以看出關(guān)聯(lián)對象的實現(xiàn)漾橙,大致是由下面四個類結(jié)合實現(xiàn)的。
AssociationsManager
AssociationsHashMap
ObjectAssociationMap
ObjcAssociation
簡單的抽取和簡化一下源碼楞卡,基本可以得出這四個類的關(guān)系霜运。
class AssociationsManager {
static AssociationsHashMap * _map
}
typedef DenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap> AssociationsHashMap;
typedef DenseMap<const void *, ObjcAssociation> ObjectAssociationMap;
class ObjcAssociation {
uintptr_t _policy;
id _value;
};
大家也可以看一下下面這幅圖。
這里很清晰的表明了這幾個類的關(guān)系臀晃,AssociationsManager有一個AssociationsHashMap類型的屬性_map觉渴,_map的key是DisguisedPtr<objc_object>類型,value是ObjectAssociationMap類型徽惋,而這個ObjectAssociationMap類型中的key是一個指針(void*),value是ObjcAssociation類型座韵。這個ObjcAssociation中有兩個重要的是就是屬性险绘,_policy和_value。
關(guān)聯(lián)對象的設(shè)置就是通過這幾個類來實現(xiàn)的誉碴,上面我們也分析完了這幾個類的相互關(guān)系宦棺,那這些類和我們在調(diào)用objc_setAssociatedObject
時傳入的參數(shù)關(guān)系是怎么樣的呢?我們繼續(xù)分析源碼黔帕。
我們在看一遍下面這方法代咸。
objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key, id _Nullable value, objc_AssociationPolicy policy)
在調(diào)用的時候我們傳入了四個參數(shù),第一個就是我們的被關(guān)聯(lián)對象成黄,第二個是我們設(shè)置的關(guān)聯(lián)對象的key呐芥, 第三個就是我們要存的值,第四個就是關(guān)聯(lián)的策略(類似retain奋岁,copy等)
這里先直接說結(jié)果吧思瘟,其實可以理解為AssociationsManager的屬性_map中,以參數(shù)object為key闻伶,value是一個ObjectAssociationMap類型的map滨攻。ObjectAssociationMap中,則是以參數(shù)中的key為key蓝翰,value是一個ObjcAssociation類型的對象光绕,最后我們參數(shù)中的value和policy就儲存在這個ObjectAssociation變量中。
好了畜份,這樣我們傳的四個參就跟這些類對應(yīng)上了诞帐。就像下圖這樣子。
注意:
通過源碼我們知道漂坏,AssociationsHashMap中的key并不是使用直接使用了object景埃,而是一個DisguisedPtr類型媒至,但是通過源碼我們可以看到DisguisedPtr<objc_object> disguised{(objc_object *)object};
,說到底這個key也是根據(jù)我們的object來生成的谷徙,所以可以說這個key與我們的object是對應(yīng)關(guān)系拒啰,從而可以理解為是這個object為key。
不得不說完慧,蘋果的設(shè)計還是很巧妙的谋旦,每一個要設(shè)置關(guān)聯(lián)對象的對象對應(yīng)一個map,在這個map中屈尼,使用我們自己設(shè)置的不同的關(guān)聯(lián)對象的key為key册着,用關(guān)聯(lián)對象的value和策略生成一個ObjcAssociation類型的對象為value,然后進(jìn)行儲存脾歧,這樣每一個要設(shè)置關(guān)聯(lián)對象的對象甲捏,具體的每一個關(guān)聯(lián)對象,都能一一對應(yīng)起來了鞭执。
上面的話說的有點拗口司顿,相信大家可以理解。
總結(jié)和補(bǔ)充
關(guān)聯(lián)對象并不是儲存在被關(guān)聯(lián)對象本身的兄纺,而是儲存在全局的統(tǒng)一的一個AssociationsHashMap中大溜。
從源碼中我們還可以看出如果我們給關(guān)聯(lián)對象設(shè)置nil,則代表移除該關(guān)聯(lián)對象估脆。
同時還要注意钦奋,因為我們的object其實是一個對象,這就要涉及內(nèi)存管理問題疙赠,當(dāng)我們的這個object釋放后付材,其實整個AssociationsHashMap中其對應(yīng)項都會被移除,這個以后我們討論內(nèi)存管理的時候再說棺聊。
objc_getAssociatedObject實現(xiàn)咱們就不具體分析了伞租,如果看動了set的實現(xiàn),get其實很容易理解限佩。
感謝閱讀葵诈。