關聯對象-給Category添加屬性

我們知道,分類無法添加成員變量,在分類中定義了屬性,系統沒有生成對應的成員變量,也沒有實現set和get方法。那我們如何實現為分類添加屬性呢?

通過runtime中提供的關聯對象相關API我們可以實現以上功能。

添加關聯對象
void objc_setAssociatedObject(id object, const void * key,
                                id value, objc_AssociationPolicy policy)

獲得關聯對象
id objc_getAssociatedObject(id object, const void * key)

移除所有的關聯對象
void objc_removeAssociatedObjects(id object)

關聯API對象參數說明:
參數一:id object : 給哪個對象添加屬性。
參數二:const void * key: 關聯對象中屬性值存取過程中對應唯一標識,根據key來設置和取值。
參數三:id value : 關聯的值,也就是set方法傳入的值給屬性去保存。
參數四: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     // 指定相關的對象被復制,原子性   
};

提供一個實例,給大家看看關聯對象的基本使用。
定義Person類,以及Person+Test分類,在Person+Test中定義name ,weight屬性,然后通過關聯對象實現set和get方法,看外部能不能正常使用?

@interface Person : NSObject

@end

@implementation Person

@end

@interface Person (Test)

@property (nonatomic, copy) NSString *name;

@property (nonatomic, assign) int weight;

@end

@implementation Person (Test)
/*
 給key設值有三種比較好的的方法
 因為key值類型為const void *,任何類型的值,其實就是給一個唯一的標識
 
 1.我們針對每個屬性,定義一個全局的key名,然后取其地址,著一定是唯一的
 加上static,只在文件內部有效
 static const void *NameKey = &NameKey;
 static const void *WeightKey = &WeightKey;
 
 2.針對每個屬性,因為類中的屬性名是唯一的,直接拿屬性名作為key
 #define NameKey = @"name";
 #define WeightKey = @"weight";
 
 3.使用@seletor作為key
 直接用屬性名對應的get方法的selector,有提示不容易寫錯
 并且get方法隱藏參數_cmd可以直接用,看上去就會更加簡潔
 以下實例代碼就是用的第三種方式
 
 */

- (void)setName:(NSString *)name{
    //通過一個key給對象關聯一個值
    objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (NSString *)name{
    //通過一個key,取出對象所關聯的值
    //隱式參數 _cmd = @selector(name)
    return objc_getAssociatedObject(self, _cmd);
}

- (void)setWeight:(int)weight{
    objc_setAssociatedObject(self, @selector(weight), @(weight), OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (int)weight{
    return [objc_getAssociatedObject(self, _cmd) intValue];
}

@end

外部調用
Person *person = [[Person alloc] init];
person.name = @"jack";
person.weight = 20;
        
NSLog(@"name:%@ weight:%d",person.name,person.weight);

打印結果
2019-07-15 17:52:27.471526+0800 給分類添加屬性-關聯對象[19511:8022622] name:jack weight:20

通過以上實例可以看出通過關聯對象是可以成功為分類添加屬性的。

那么使用關聯對象是將屬性添加到原有類的內存中,還是另外通過其他方式保存呢?如果通過其他方式,又是如何保存,如何銷毀的呢?

關聯對象原理

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

  1. AssociationsManager
  2. AssociationsHashMap
  3. ObjectAssociationMap
  4. ObjcAssociation
    其中Map就相當于我們平時使用的字典,也是key-value存取值。

我們通過runtime源碼來分析底層原理,來到objc-references.mm,搜索objc_setAssociatedObjectobjc_getAssociatedObjectobjc_removeAssociatedObjects,分別來看看設置,取值,移除所有關聯對象的底層邏輯。

/**********************************************************************
* Associative Reference Support
**********************************************************************/

id objc_getAssociatedObject(id object, const void *key) {
    return _object_get_associative_reference(object, (void *)key);
}


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


void objc_removeAssociatedObjects(id object) 
{
    if (object && object->hasAssociatedObjects()) {
        _object_remove_assocations(object);
    }
}
_object_set_associative_reference函數
_object_set_associative_reference函數

_object_set_associative_reference函數內部我們可以找到上面說過的實現關聯對象技術的核心對象。接下來我們來AssociationManager、AssociationsHashMap、ObjectAssociationMap、ObjcAssociation之間的關系。

AssociationManager
AssociationsManager源碼
AssociationsHashMap、ObjectAssociationMap、ObjcAssociation
AssociationsHashMap和ObjectAssociationMap源碼

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

unordered_map部分源碼

從unordered_map源碼中可以看出前兩個參數_key,_tp對應的就是map中的key和value,參照傳入參數,_key對應的值就是disguised_ptr_t,_Tp 對應的值就是ObjectAssociationMap *

接著我們查看ObjectAssociationMap,繼承至map,和unordered_map類似,同樣以key,value方式存儲著ObjectAssociation。_key對應著void *,_Tp 對應著ObjectAssociation

再接著來查看ObjcAssociation。


ObjcAssociation源碼

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

現在我們已經對AssociationsManager、 AssociationsHashMap、 ObjectAssociationMap、ObjcAssociation四個對象之間的關系有了簡單的認識,那么接下來我們來細讀源碼。


_object_set_associative_reference函數精讀

最后通過一張圖整理其中關系,這樣就看的更加清晰明了。


關聯對象原理圖

關聯對象并不是存儲在關聯對象本身內存中
關聯對象存儲在全局的一個AssociationsManager中
設置關聯對象為nil,就相當于移除了關聯對象

_object_get_associative_reference函數

_object_get_associative_reference函數

_object_remove_assocations函數

_object_remove_assocations函數

結合以上總結的關聯對象原理圖,就能很好理解_object_get_associative_reference_object_remove_assocations的邏輯。

總結:
關聯對象并不是存儲在關聯對象本身內存中
關聯對象存儲在全局的一個AssociationsManager中
設置關聯對象為nil,就相當于移除了關聯對象

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,882評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,208評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,746評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,666評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,477評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,960評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,047評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,200評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,726評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,617評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,807評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,327評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,049評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,425評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,674評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,432評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,769評論 2 372