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

相關(guān)文章

iOS底層源碼探索的方式

面試題

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

RunTime為Category動態(tài)關(guān)聯(lián)對象

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

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

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

@property(nonatomic,strong)NSString *name;

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

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

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

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

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

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

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

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

參數(shù)一:id object : 給哪個對象添加屬性,這里要給自己添加屬性,用self。
參數(shù)二:void * == id key : 屬性名,根據(jù)key獲取關(guān)聯(lián)對象的屬性的值,在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,  // 指定一個弱引用相關(guān)聯(lián)的對象
    OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, // 指定相關(guān)對象的強引用,非原子性
    OBJC_ASSOCIATION_COPY_NONATOMIC = 3,  // 指定相關(guān)的對象被復(fù)制,非原子性
    OBJC_ASSOCIATION_RETAIN = 01401,  // 指定相關(guān)對象的強引用,原子性
    OBJC_ASSOCIATION_COPY = 01403     // 指定相關(guān)的對象被復(fù)制,原子性   
};

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

1. 獲得屬性

objc_getAssociatedObject(id object, const void *key);

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

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

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

此時已經(jīng)成功給NSObject添加name屬性,并且NSObject對象可以通過點語法為屬性賦值。

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

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

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

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

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

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

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

objc_setAssociatedObject函數(shù)

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


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

我們看到其實內(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)部我們可以全部找到我們上面說過的實現(xiàn)關(guān)聯(lián)對象技術(shù)的核心對象。接下來我們來一個一個看其內(nèi)部實現(xiàn)原理探尋他們之間的關(guān)系。

AssociationsManager

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

AssociationsManager內(nèi)部

AssociationsHashMap

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


AssociationsHashMap內(nèi)部

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

unordered_map內(nèi)部分源碼

從unordered_map源碼中我們可以看出_Key和_Tp也就是前兩個參數(shù)對應(yīng)著map中的Key和Value,那么對照上面AssociationsHashMap內(nèi)源碼發(fā)現(xiàn)_Key中傳入的是disguised_ptr_t,_Tp中傳入的值則為ObjectAssociationMap*。

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


ObjcAssociation

我們發(fā)現(xiàn)ObjcAssociation存儲著_policy、_value,而這兩個值我們可以發(fā)現(xiàn)正是我們調(diào)用objc_setAssociatedObject函數(shù)傳入的值,也就是說我們在調(diào)用objc_setAssociatedObject函數(shù)中傳入的value和policy這兩個值最終是存儲在ObjcAssociation中的。

現(xiàn)在我們已經(jīng)對AssociationsManager、 AssociationsHashMap、 ObjectAssociationMap、ObjcAssociation四個對象之間的關(guān)系有了簡單的認識,那么接下來我們來細讀源碼,看一下objc_setAssociatedObject函數(shù)中傳入的四個參數(shù)分別放在哪個對象中充當什么作用。

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


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

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


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

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


DISGUISE函數(shù)

DISGUISE函數(shù)其實僅僅對object做了位運算

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


關(guān)鍵代碼

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


value為nil

從上述代碼中可以看出,如果我們設(shè)置value為nil時,就會將關(guān)聯(lián)對象從ObjectAssociationMap中移除。

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


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

通過上圖我們可以總結(jié)為:一個實例對象就對應(yīng)一個ObjectAssociationMap,而ObjectAssociationMap中存儲著多個此實例對象的關(guān)聯(lián)對象的key以及ObjcAssociation,為ObjcAssociation中存儲著關(guān)聯(lián)對象的value和policy策略。

由此我們可以知道關(guān)聯(lián)對象并不是放在了原來的對象里面,而是自己維護了一個全局的map用來存放每一個對象及其對應(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)對象,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對象向?qū)?yīng)的所有關(guān)聯(lián)對象全部刪除。

總結(jié):

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

此時我們我們在回過頭來看objc_AssociationPolicy policy參數(shù): 屬性以什么形式保存的策略。

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

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

disguised_ptr_t disguised_object = DISGUISE(object);

而同時我們知道,weak修飾的屬性,當沒有擁有對象之后就會被銷毀,并且指針置位nil,那么在對象銷毀之后,雖然在map中既然存在值object對應(yīng)的AssociationsHashMap,但是因為object地址已經(jīng)被置位nil,會造成壞地址訪問而無法根據(jù)object對象的地址轉(zhuǎn)化為disguised_object了。

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容