iOS底層原理總結 - 關聯對象實現原理

面試題

問: Category可否添加成員變量?若是能夠,如何給Category添加成員變量?面試

答:不能直接添加成員變量,可是能夠經過runtime的方式間接實現添加成員變量的效果。bash

RunTime爲Category動態關聯對象

使用RunTime給系統的類添加屬性,首先須要瞭解對象與屬性的關係。咱們經過以前的學習知道,對象一開始初始化的時候其屬性爲nil,給屬性賦值其實就是讓屬性指向一塊存儲內容的內存,使這個對象的屬性跟這塊內存產生一種關聯。函數

那麼若是想動態的添加屬性,其實就是動態的產生某種關聯就行了。而想要給系統的類添加屬性,只能經過分類。post

這裏給NSObject添加name屬性,建立NSObject的分類 咱們可使用@property給分類添加屬性學習

@property(nonatomic,strong)NSString *name;
複製代碼

經過探尋Category的本質咱們知道,雖然在分類中能夠寫@property 添加屬性,可是不會自動生成私有屬性,也不會生成set,get方法的實現,只會生成set,get的聲明,須要咱們本身去實現。ui

方法一:咱們能夠經過使用靜態全局變量給分類添加屬性
static NSString *_name;
-(void)setName:(NSString *)name
{
    _name = name;
}
-(NSString *)name
{
    return _name;
}
複製代碼

可是這樣_name靜態全局變量與類並無關聯,不管對象建立與銷燬,只要程序在運行_name變量就存在,並非真正意義上的屬性。atom

方法二:使用RunTime動態添加屬性

RunTime提供了動態添加屬性和得到屬性的方法。spa

-(void)setName:(NSString *)name
{
    objc_setAssociatedObject(self, @"name",name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-(NSString *)name
{
    return objc_getAssociatedObject(self, @"name");    
}
複製代碼
  1. 動態添加屬性
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);
複製代碼

參數一:id object : 給哪一個對象添加屬性,這裏要給本身添加屬性,用self。 參數二:void * == id key : 屬性名,根據key獲取關聯對象的屬性的值,在**objc_getAssociatedObject中經過次key得到屬性的值並返回。 參數三:id value** : 關聯的值,也就是set方法傳入的值給屬性去保存。 參數四:objc_AssociationPolicy policy : 策略,屬性以什麼形式保存。 有如下幾種3d

typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
    OBJC_ASSOCIATION_ASSIGN = 0,  // 指定一個弱引用相關聯的對象
    OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, // 指定相關對象的強引用,非原子性
    OBJC_ASSOCIATION_COPY_NONATOMIC = 3,  // 指定相關的對象被複制,非原子性
    OBJC_ASSOCIATION_RETAIN = 01401,  // 指定相關對象的強引用,原子性
    OBJC_ASSOCIATION_COPY = 01403     // 指定相關的對象被複制,原子性   
};
複製代碼

key值只要是一個指針便可,咱們能夠傳入@selector(name)指針

  1. 得到屬性
objc_getAssociatedObject(id object, const void *key);
複製代碼

參數一:id object : 獲取哪一個對象裏面的關聯的屬性。 參數二:void * == id key : 什麼屬性,與**objc_setAssociatedObject**中的key相對應,即經過key值取出value。

  1. 移除全部關聯對象
- (void)removeAssociatedObjects
{
    // 移除全部關聯對象
    objc_removeAssociatedObjects(self);
}

複製代碼

此時已經成功給NSObject添加name屬性,而且NSObject對象能夠經過點語法爲屬性賦值。

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

能夠看出關聯對象的使用很是簡單,接下來咱們來探尋關聯對象的底層原理

關聯對象實現原理

實現關聯對象技術的核心對象有

  1. AssociationsManager
  2. AssociationsHashMap
  3. ObjectAssociationMap
  4. ObjcAssociation
    其中Map同咱們平時使用的字典相似。經過key-value一一對應存值。

對關聯對象技術的核心對象有了一個大概的意識,咱們經過源碼來探尋這些對象的存在形式以及其做用

objc_setAssociatedObject函數

來到runtime源碼,首先找到objc_setAssociatedObject函數,看一下其實現

objc_setAssociatedObject函數實現

咱們看到其實內部調用的是_object_set_associative_reference函數,咱們來到_object_set_associative_reference函數中

_object_set_associative_reference函數

_object_set_associative_reference函數內部
_object_set_associative_reference函數內部咱們能夠所有找到咱們上面說過的實現關聯對象技術的核心對象。接下來咱們來一個一個看其內部實現原理探尋他們之間的關係。

AssociationsManager

經過AssociationsManager內部源碼發現,AssociationsManager內部有一個AssociationsHashMap對象。

AssociationsManager內部

AssociationsHashMap

咱們來看一下AssociationsHashMap內部的源碼。

AssociationsHashMap內部

經過AssociationsHashMap內部源碼咱們發現AssociationsHashMap繼承自unordered_map首先來看一下unordered_map內的源碼

unordered_map內部分源碼

從unordered_map源碼中咱們能夠看出_Key_Tp也就是前兩個參數對應着map中的Key和Value,那麼對照上面AssociationsHashMap內源碼發現_Key中傳入的是disguised_ptr_t_Tp中傳入的值則爲ObjectAssociationMap*

緊接着咱們來到ObjectAssociationMap中,上圖中ObjectAssociationMap已經標記出,咱們發現ObjectAssociationMap中一樣以key、Value的方式存儲着ObjcAssociation

接着咱們來到ObjcAssociation中

ObjcAssociation

咱們發現ObjcAssociation存儲着_policy_value,而這兩個值咱們能夠發現正是咱們調用objc_setAssociatedObject函數傳入的值,也就是說咱們在調用objc_setAssociatedObject函數中傳入的value和policy這兩個值最終是存儲在ObjcAssociation中的。

如今咱們已經對AssociationsManager、 AssociationsHashMap、 ObjectAssociationMap、ObjcAssociation四個對象之間的關係有了簡單的認識,那麼接下來咱們來細讀源碼,看一下objc_setAssociatedObject函數中傳入的四個參數分別放在哪一個對象中充當什麼做用。

從新回到_object_set_associative_reference函數實現中

_object_set_associative_reference函數內部

細讀上述源碼咱們能夠發現,首先根據咱們傳入的value通過acquireValue函數處理獲取new_value。acquireValue函數內部實際上是經過對策略的判斷返回不一樣的值

acquireValue函數內部

以後建立AssociationsManager manager;以及拿到manager內部的AssociationsHashMap即associations。 以後咱們看到了咱們傳入的第一個參數object object通過DISGUISE函數被轉化爲了disguised_ptr_t類型的disguised_object

DISGUISE函數

DISGUISE函數其實僅僅對object作了位運算

以後咱們看到被處理成new_value的value,同policy被存入了ObjcAssociation中。 而ObjcAssociation對應咱們傳入的key被存入了ObjectAssociationMap中。 disguised_object和ObjectAssociationMap則以key-value的形式對應存儲在associations中也就是AssociationsHashMap中。

關鍵代碼

若是咱們value設置爲nil的話那麼會執行下面的代碼

value爲nil

從上述代碼中能夠看出,若是咱們設置value爲nil時,就會將關聯對象從ObjectAssociationMap中移除。

最後咱們經過一張圖能夠很清晰的理清楚其中的關係

關聯對象底層對象關係

經過上圖咱們能夠總結爲:一個實例對象就對應一個ObjectAssociationMap,而ObjectAssociationMap中存儲着多個此實例對象的關聯對象的key以及ObjcAssociation,爲ObjcAssociation中存儲着關聯對象的value和policy策略。

由此咱們能夠知道關聯對象並非放在了原來的對象裏面,而是本身維護了一個全局的map用來存放每個對象及其對應關聯屬性表格。

objc_getAssociatedObject函數

objc_getAssociatedObject內部調用的是_object_get_associative_reference

objc_getAssociatedObject

_object_get_associative_reference函數

_object_get_associative_reference函數

從_object_get_associative_reference函數內部能夠看出,向set方法中那樣,反向將value一層一層取出最後return出去。

objc_removeAssociatedObjects函數

objc_removeAssociatedObjects用來刪除全部的關聯對象,objc_removeAssociatedObjects函數內部調用的是_object_remove_assocations函數

objc_removeAssociatedObjects函數

_object_remove_assocations函數

_object_remove_assocations函數

上述源碼能夠看出_object_remove_assocations函數將object對象向對應的全部關聯對象所有刪除。

總結:

關聯對象並非存儲在被關聯對象自己內存中,而是存儲在全局的統一的一個AssociationsManager中,若是設置關聯對象爲nil,就至關因而移除關聯對象。

此時咱們咱們在回過頭來看objc_AssociationPolicy policy 參數: 屬性以什麼形式保存的策略。

typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
    OBJC_ASSOCIATION_ASSIGN = 0,  // 指定一個弱引用相關聯的對象
    OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, // 指定相關對象的強引用,非原子性
    OBJC_ASSOCIATION_COPY_NONATOMIC = 3,  // 指定相關的對象被複制,非原子性
    OBJC_ASSOCIATION_RETAIN = 01401,  // 指定相關對象的強引用,原子性
    OBJC_ASSOCIATION_COPY = 01403     // 指定相關的對象被複制,原子性   
};
複製代碼

咱們會發現其中只有RETAIN和COPY而爲何沒有weak呢? 總過上面對源碼的分析咱們知道,object通過DISGUISE函數被轉化爲了disguised_ptr_t類型的disguised_object

disguised_ptr_t disguised_object = DISGUISE(object);
複製代碼

而同時咱們知道,weak修飾的屬性,當沒有擁有對象以後就會被銷燬,而且指針置位nil,那麼在對象銷燬以後,雖然在map中既然存在值object對應的AssociationsHashMap,可是由於object地址已經被置位nil,會形成壞地址訪問而沒法根據object對象的地址轉化爲disguised_object了。


本文是對底層原理學習的總結,若是有不對的地方請指正,歡迎你們一塊兒交流學習 xx_cc 。須要這套視頻一塊兒交流學習的能夠加我Q:2336684744

相關文章
相關標籤/搜索