iOS開發(fā)-底層原理總結(jié) - 關(guān)聯(lián)對(duì)象實(shí)現(xiàn)原理

相關(guān)文章

iOS底層源碼探索的方式

面試題

  1. Category能否添加成員變量?如果可以,如何給Category添加成員變量包颁?
    答:不能直接添加成員變量,但是可以通過runtime的方式間接實(shí)現(xiàn)添加成員變量的效果。

RunTime為Category動(dòng)態(tài)關(guān)聯(lián)對(duì)象

使用RunTime給系統(tǒng)的類添加屬性齐遵,首先需要了解對(duì)象與屬性的關(guān)系。我們通過之前的學(xué)習(xí)知道塔插,對(duì)象一開始初始化的時(shí)候其屬性為nil梗摇,給屬性賦值其實(shí)就是讓屬性指向一塊存儲(chǔ)內(nèi)容的內(nèi)存,使這個(gè)對(duì)象的屬性跟這塊內(nèi)存產(chǎn)生一種關(guān)聯(lián)想许。

那么如果想動(dòng)態(tài)的添加屬性伶授,其實(shí)就是動(dòng)態(tài)的產(chǎn)生某種關(guān)聯(lián)就好了断序。而想要給系統(tǒng)的類添加屬性,只能通過分類糜烹。

這里給NSObject添加name屬性违诗,創(chuàng)建NSObject的分類
我們可以使用@property給分類添加屬性

@property(nonatomic,strong)NSString *name;

通過探尋Category的本質(zhì)我們知道,雖然在分類中可以寫@property
添加屬性疮蹦,但是不會(huì)自動(dòng)生成私有屬性诸迟,也不會(huì)生成set,get方法的實(shí)現(xiàn),只會(huì)生成set,get的聲明挚币,需要我們自己去實(shí)現(xiàn)亮蒋。

  • 方法一:我們可以通過使用靜態(tài)全局變量給分類添加屬性
static NSString *_name;
-(void)setName:(NSString *)name
{
    _name = name;
}
-(NSString *)name
{
    return _name;
}

但是這樣_name靜態(tài)全局變量與類并沒有關(guān)聯(lián),無論對(duì)象創(chuàng)建與銷毀妆毕,只要程序在運(yùn)行_name變量就存在慎玖,并不是真正意義上的屬性。

  • 方法二:使用RunTime動(dòng)態(tài)添加屬性

RunTime提供了動(dòng)態(tài)添加屬性和獲得屬性的方法笛粘。

-(void)setName:(NSString *)name
{
    objc_setAssociatedObject(self, @"name",name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-(NSString *)name
{
    return objc_getAssociatedObject(self, @"name");    
}

1. 動(dòng)態(tài)添加屬性

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

參數(shù)一:id object : 給哪個(gè)對(duì)象添加屬性趁怔,這里要給自己添加屬性,用self薪前。
參數(shù)二:void * == id key : 屬性名润努,根據(jù)key獲取關(guān)聯(lián)對(duì)象的屬性的值,在objc_getAssociatedObject中通過次key獲得屬性的值并返回示括。
參數(shù)三:id value : 關(guān)聯(lián)的值铺浇,也就是set方法傳入的值給屬性去保存。
參數(shù)四:objc_AssociationPolicy policy : 策略垛膝,屬性以什么形式保存鳍侣。
有以下幾種

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

key值只要是一個(gè)指針即可凿可,我們可以傳入@selector(name)

1. 獲得屬性

objc_getAssociatedObject(id object, const void *key);

參數(shù)一:id object : 獲取哪個(gè)對(duì)象里面的關(guān)聯(lián)的屬性惑折。
參數(shù)二:void * == id key : 什么屬性,與objc_setAssociatedObject中的key相對(duì)應(yīng)枯跑,即通過key值取出value惨驶。

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

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

此時(shí)已經(jīng)成功給NSObject添加name屬性,并且NSObject對(duì)象可以通過點(diǎn)語法為屬性賦值敛助。

NSObject *objc = [[NSObject alloc]init];
objc.name = @"xx_cc";
NSLog(@"%@",objc.name);

可以看出關(guān)聯(lián)對(duì)象的使用非常簡(jiǎn)單敞咧,接下來我們來探尋關(guān)聯(lián)對(duì)象的底層原理

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

實(shí)現(xiàn)關(guān)聯(lián)對(duì)象技術(shù)的核心對(duì)象有

  1. AssociationsManager
  2. AssociationsHashMap
  3. ObjectAssociationMap
  4. ObjcAssociation

其中Map同我們平時(shí)使用的字典類似。通過key-value一一對(duì)應(yīng)存值辜腺。

對(duì)關(guān)聯(lián)對(duì)象技術(shù)的核心對(duì)象有了一個(gè)大概的意識(shí)休建,我們通過源碼來探尋這些對(duì)象的存在形式以及其作用

objc_setAssociatedObject函數(shù)

來到runtime源碼,首先找到objc_setAssociatedObject函數(shù)评疗,看一下其實(shí)現(xiàn)


objc_setAssociatedObject函數(shù)實(shí)現(xiàn)

我們看到其實(shí)內(nèi)部調(diào)用的是_object_set_associative_reference函數(shù)测砂,我們來到_object_set_associative_reference函數(shù)中

_object_set_associative_reference函數(shù)

_object_set_associative_reference函數(shù)內(nèi)部

_object_set_associative_reference函數(shù)內(nèi)部我們可以全部找到我們上面說過的實(shí)現(xiàn)關(guān)聯(lián)對(duì)象技術(shù)的核心對(duì)象。接下來我們來一個(gè)一個(gè)看其內(nèi)部實(shí)現(xiàn)原理探尋他們之間的關(guān)系百匆。

AssociationsManager

通過AssociationsManager內(nèi)部源碼發(fā)現(xiàn)砌些,AssociationsManager內(nèi)部有一個(gè)AssociationsHashMap對(duì)象。

AssociationsManager內(nèi)部

AssociationsHashMap

我們來看一下AssociationsHashMap內(nèi)部的源碼加匈。


AssociationsHashMap內(nèi)部

通過AssociationsHashMap內(nèi)部源碼我們發(fā)現(xiàn)AssociationsHashMap繼承自u(píng)nordered_map首先來看一下unordered_map內(nèi)的源碼

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*雕拼。

緊接著我們來到ObjectAssociationMap中纵东,上圖中ObjectAssociationMap已經(jīng)標(biāo)記出,我們發(fā)現(xiàn)ObjectAssociationMap中同樣以key啥寇、Value的方式存儲(chǔ)著ObjcAssociation偎球。
接著我們來到ObjcAssociation中


ObjcAssociation

我們發(fā)現(xiàn)ObjcAssociation存儲(chǔ)著_policy、_value辑甜,而這兩個(gè)值我們可以發(fā)現(xiàn)正是我們調(diào)用objc_setAssociatedObject函數(shù)傳入的值衰絮,也就是說我們?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í)邓线,那么接下來我們來細(xì)讀源碼淌友,看一下objc_setAssociatedObject函數(shù)中傳入的四個(gè)參數(shù)分別放在哪個(gè)對(duì)象中充當(dāng)什么作用。

重新回到_object_set_associative_reference函數(shù)實(shí)現(xiàn)中


_object_set_associative_reference函數(shù)內(nèi)部

細(xì)讀上述源碼我們可以發(fā)現(xiàn)褂痰,首先根據(jù)我們傳入的value經(jīng)過acquireValue函數(shù)處理獲取new_value亩进。acquireValue函數(shù)內(nèi)部其實(shí)是通過對(duì)策略的判斷返回不同的值


acquireValue函數(shù)內(nèi)部

之后創(chuàng)建AssociationsManager manager;以及拿到manager內(nèi)部的AssociationsHashMap即associations。
之后我們看到了我們傳入的第一個(gè)參數(shù)object
object經(jīng)過DISGUISE函數(shù)被轉(zhuǎn)化為了disguised_ptr_t類型的disguised_object缩歪。


DISGUISE函數(shù)

DISGUISE函數(shù)其實(shí)僅僅對(duì)object做了位運(yùn)算

之后我們看到被處理成new_value的value归薛,同policy被存入了ObjcAssociation中。
而ObjcAssociation對(duì)應(yīng)我們傳入的key被存入了ObjectAssociationMap中匪蝙。
disguised_object和ObjectAssociationMap則以key-value的形式對(duì)應(yīng)存儲(chǔ)在associations中也就是AssociationsHashMap中主籍。


關(guān)鍵代碼

如果我們value設(shè)置為nil的話那么會(huì)執(zhí)行下面的代碼


value為nil

從上述代碼中可以看出,如果我們?cè)O(shè)置value為nil時(shí)逛球,就會(huì)將關(guān)聯(lián)對(duì)象從ObjectAssociationMap中移除千元。

最后我們通過一張圖可以很清晰的理清楚其中的關(guān)系


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

通過上圖我們可以總結(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策略幸海。

由此我們可以知道關(guān)聯(lián)對(duì)象并不是放在了原來的對(duì)象里面祟身,而是自己維護(hù)了一個(gè)全局的map用來存放每一個(gè)對(duì)象及其對(duì)應(yīng)關(guān)聯(lián)屬性表格。

objc_getAssociatedObject函數(shù)

objc_getAssociatedObject內(nèi)部調(diào)用的是_object_get_associative_reference


objc_getAssociatedObject

_object_get_associative_reference函數(shù)


_object_get_associative_reference函數(shù)

從_object_get_associative_reference函數(shù)內(nèi)部可以看出物独,向set方法中那樣袜硫,反向?qū)alue一層一層取出最后return出去。

objc_removeAssociatedObjects函數(shù)

objc_removeAssociatedObjects用來刪除所有的關(guān)聯(lián)對(duì)象挡篓,objc_removeAssociatedObjects函數(shù)內(nèi)部調(diào)用的是_object_remove_assocations函數(shù)


objc_removeAssociatedObjects函數(shù)

_object_remove_assocations函數(shù)

_object_remove_assocations函數(shù)

上述源碼可以看出_object_remove_assocations函數(shù)將object對(duì)象向?qū)?yīng)的所有關(guān)聯(lián)對(duì)象全部刪除婉陷。

總結(jié):

關(guān)聯(lián)對(duì)象并不是存儲(chǔ)在被關(guān)聯(lián)對(duì)象本身內(nèi)存中,而是存儲(chǔ)在全局的統(tǒng)一的一個(gè)AssociationsManager中官研,如果設(shè)置關(guān)聯(lián)對(duì)象為nil秽澳,就相當(dāng)于是移除關(guān)聯(lián)對(duì)象。

此時(shí)我們我們?cè)诨剡^頭來看objc_AssociationPolicy policy參數(shù): 屬性以什么形式保存的策略戏羽。

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

我們會(huì)發(fā)現(xiàn)其中只有RETAIN和COPY而為什么沒有weak呢?
總過上面對(duì)源碼的分析我們知道衙荐,object經(jīng)過DISGUISE函數(shù)被轉(zhuǎn)化為了disguised_ptr_t類型的disguised_object捞挥。

disguised_ptr_t disguised_object = DISGUISE(object);

而同時(shí)我們知道,weak修飾的屬性忧吟,當(dāng)沒有擁有對(duì)象之后就會(huì)被銷毀砌函,并且指針置位nil,那么在對(duì)象銷毀之后溜族,雖然在map中既然存在值object對(duì)應(yīng)的AssociationsHashMap讹俊,但是因?yàn)閛bject地址已經(jīng)被置位nil,會(huì)造成壞地址訪問而無法根據(jù)object對(duì)象的地址轉(zhuǎn)化為disguised_object了煌抒。

原文鏈接:iOS底層原理總結(jié) - 關(guān)聯(lián)對(duì)象實(shí)現(xiàn)原理

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末仍劈,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子寡壮,更是在濱河造成了極大的恐慌贩疙,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,482評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件况既,死亡現(xiàn)場(chǎng)離奇詭異这溅,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)棒仍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門悲靴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人莫其,你說我怎么就攤上這事癞尚∷嗜” “怎么了?”我有些...
    開封第一講書人閱讀 152,762評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵浇揩,是天一觀的道長(zhǎng)吕晌。 經(jīng)常有香客問我,道長(zhǎng)临燃,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,273評(píng)論 1 279
  • 正文 為了忘掉前任烙心,我火速辦了婚禮膜廊,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘淫茵。我一直安慰自己爪瓜,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評(píng)論 5 373
  • 文/花漫 我一把揭開白布匙瘪。 她就那樣靜靜地躺著铆铆,像睡著了一般。 火紅的嫁衣襯著肌膚如雪丹喻。 梳的紋絲不亂的頭發(fā)上薄货,一...
    開封第一講書人閱讀 49,046評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音碍论,去河邊找鬼谅猾。 笑死,一個(gè)胖子當(dāng)著我的面吹牛鳍悠,可吹牛的內(nèi)容都是我干的税娜。 我是一名探鬼主播,決...
    沈念sama閱讀 38,351評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼藏研,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼敬矩!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蠢挡,我...
    開封第一講書人閱讀 36,988評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤弧岳,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后袒哥,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體缩筛,經(jīng)...
    沈念sama閱讀 43,476評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評(píng)論 2 324
  • 正文 我和宋清朗相戀三年堡称,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了瞎抛。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,064評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡却紧,死狀恐怖桐臊,靈堂內(nèi)的尸體忽然破棺而出胎撤,到底是詐尸還是另有隱情,我是刑警寧澤断凶,帶...
    沈念sama閱讀 33,712評(píng)論 4 323
  • 正文 年R本政府宣布伤提,位于F島的核電站,受9級(jí)特大地震影響认烁,放射性物質(zhì)發(fā)生泄漏肿男。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評(píng)論 3 307
  • 文/蒙蒙 一却嗡、第九天 我趴在偏房一處隱蔽的房頂上張望舶沛。 院中可真熱鬧,春花似錦窗价、人聲如沸如庭。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽坪它。三九已至,卻和暖如春帝牡,著一層夾襖步出監(jiān)牢的瞬間往毡,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工否灾, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留卖擅,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,511評(píng)論 2 354
  • 正文 我出身青樓墨技,卻偏偏與公主長(zhǎng)得像惩阶,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子扣汪,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評(píng)論 2 345

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