09 iOS底層原理 - 給Category添加成員變量

廢話(huà)不多說(shuō)平道,老規(guī)矩,還是先來(lái)道面試題:

一供炼,Category能否添加成員變量一屋?如果可以,如何給Category添加成員變量袋哼?

帶著問(wèn)題咋們來(lái)看看分類(lèi)到底能不能添加成員變量...

咋們先看看類(lèi)和分類(lèi)的底層數(shù)據(jù)結(jié)構(gòu)張啥樣吧:
具體分析請(qǐng)看我之前的博客:
02 iOS底層原理 - isa和superclass指針探究
05 iOS底層原理 - Category本質(zhì)探究

一冀墨,分析一下分類(lèi)的底層數(shù)據(jù)結(jié)構(gòu)

1. 類(lèi)的底層數(shù)據(jù)結(jié)構(gòu)
image.png

這是從runtime源碼里面找到的對(duì)象數(shù)據(jù)結(jié)構(gòu),可以看出涛贯,是可以存儲(chǔ)在成員變量的诽嘉。

2. 分類(lèi)底層數(shù)據(jù)結(jié)構(gòu)
image.png

這是clang編譯后的數(shù)據(jù)結(jié)構(gòu),當(dāng)然了也可以從runtime里面找的到疫蔓,可以看出含懊,這個(gè)結(jié)構(gòu)里面是沒(méi)有提供成員變量列表的身冬。所以衅胀,分類(lèi)從結(jié)構(gòu)上就沒(méi)法添加成員變量。

但是酥筝,有些人呢滚躯,就想用分類(lèi),還想給分類(lèi)添加個(gè)成員變量嘿歌,這該咋整呢掸掏??宙帝?丧凤?

二,利用runtime關(guān)聯(lián)對(duì)象添加和獲取值

在說(shuō)關(guān)聯(lián)對(duì)象前呢步脓,我還是簡(jiǎn)單說(shuō)下愿待,除了利用關(guān)聯(lián)對(duì)象添加外浩螺,其實(shí)還可以用以下幾種形式添加:

1. 利用字典添加
// 聲明
@interface Person (Test1)
@property (nonatomic, assign) int height;
@property (nonatomic, copy) NSString *name;
@end

// 實(shí)現(xiàn)
@implementation Person (Test1)
#define p_key [NSString stringWithFormat:@"%p", self]
// 使用字典存儲(chǔ)
NSMutableDictionary *heights_;
NSMutableDictionary *names_;

+(void)load {
    heights_ = [NSMutableDictionary dictionary];
    names_ = [NSMutableDictionary dictionary];
}

- (void)setHeight:(int)height {
    heights_[p_key] = @(height);
}

- (void)setName:(NSString *)name {
    names_[p_key] = name;
}

- (int)height {
    return [heights_[p_key] intValue];
}

- (NSString *)name {
    return names_[p_key];
}

其實(shí)利用字典添加成員變量,還是很好理解的仍侥,就是講setter方法里面的key作為值存儲(chǔ)在字典里面要出,而字典的key則是當(dāng)前self的地址值。

但是在真正實(shí)現(xiàn)的時(shí)候农渊,還是很繁瑣的患蹂,每增加一個(gè)屬性,都要聲明一個(gè)字典砸紊、初始化传于、加鎖、存儲(chǔ)醉顽、解鎖等操作格了,所以還是不建議直接用字典的。

字典都不建議使用了徽鼎,那么用全局變量來(lái)添加成員變量就更復(fù)雜和不安全了盛末。

那么,有沒(méi)有更簡(jiǎn)單的方式否淤,來(lái)添加成員變量呢悄但??石抡?
肯定是有的檐嚣,我們可以間接的用runtime給分類(lèi)添加成員變量。

2. runtime關(guān)聯(lián)對(duì)象添加成員變量

runtime提供了三個(gè)關(guān)聯(lián)對(duì)象的API:

1> 設(shè)置關(guān)聯(lián)對(duì)象
OBJC_EXPORT void
objc_setAssociatedObject(id _Nonnull object,
                         const void * _Nonnull key,
                         id _Nullable value,
                         objc_AssociationPolicy policy)
    OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0, 2.0);
2>獲取關(guān)聯(lián)對(duì)象
OBJC_EXPORT id _Nullable
objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key)
    OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0, 2.0);
3> 刪除關(guān)聯(lián)對(duì)象
OBJC_EXPORT void
objc_removeAssociatedObjects(id _Nonnull object)
    OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0, 2.0);
4> 利用關(guān)聯(lián)對(duì)象添加成員變量
- (void)setName:(NSString *)name {
    objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY);
}

- (NSString *)name {
    return objc_getAssociatedObject(self, @selector(name));
}

- (void)setHeight:(int)height {
    objc_setAssociatedObject(self, @selector(height), @(height), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (int)height {
    return [objc_getAssociatedObject(self, @selector(height)) intValue];
}

簡(jiǎn)單說(shuō)下利用 @selector(property) 作為key的原因:

  • 這個(gè)key其實(shí)沒(méi)有實(shí)際的意義啰扛,只要能唯一的表示是當(dāng)前的這個(gè)屬性值即可嚎京;
  • objc_setAssociatedObject中的入?yún)ey的聲明是這樣的,
    const void * _Nonnull隐解,需要傳入一個(gè)地址鞍帝,而@selector(property)其實(shí)就是這個(gè)property 的getter方法地址,并且唯一煞茫。

運(yùn)行代碼:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    Person *person1 = [[Person alloc]init];
    person1.age = 18;
    person1.name = @"張三";
    person1.height = 165;
    
    Person *person2 = [[Person alloc]init];
    person2.age = 28;
    person2.name = @"李四";
    person2.height = 178;
    
    NSLog(@"person1: age = %d, name = %@, height = %d", person1.age, person1.name, person1.height);
    NSLog(@"person2: age = %d, name = %@, height = %d", person2.age, person2.name, person2.height);
}

打印結(jié)果:

person1: age = 18, name = 張三, height = 165
person2: age = 28, name = 李四, height = 178

是不是可以完美的解決帕涌,分類(lèi)不能直接添加成員變量的問(wèn)題了。

那么续徽,下面就看看源碼是怎么來(lái)添加這個(gè)所謂的”成員變量“的...

三蚓曼,runtime源碼分析

源碼下載
找數(shù)字最大的下載,排在前面的不一定是最新的

1. 入?yún)⒄f(shuō)明

首先钦扭,看下

OBJC_EXPORT void
objc_setAssociatedObject(id _Nonnull object,
                         const void * _Nonnull key,
                         id _Nullable value,
                         objc_AssociationPolicy policy)
    OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0, 2.0);

每個(gè)入?yún)⒌囊馑迹?/p>

para1:object:需要關(guān)聯(lián)的對(duì)象
para2:key:需要關(guān)聯(lián)值的key
para3:value:需要關(guān)聯(lián)的值
para4:policy:以什么樣的策略關(guān)聯(lián)值

其中policy參數(shù)和修飾符的對(duì)應(yīng)關(guān)系如下:

image.png
2. 源碼查找流程
image.png
3. 核心代碼解讀

直接上兩張我總結(jié)的圖:

image.png
image.png

還是簡(jiǎn)單說(shuō)下上面一張圖吧:
關(guān)聯(lián)對(duì)象的本質(zhì)纫版,其實(shí)和字典存儲(chǔ)很像

  • 每個(gè)分類(lèi)的對(duì)象作為key存儲(chǔ)在第一級(jí)字典里,value則是存儲(chǔ)當(dāng)前對(duì)象里所有的屬性客情,這個(gè)value是一個(gè)字典ObjectAssociationMap其弊;
  • 入?yún)ey作為ObjectAssociationMap的key会涎,屬性值value和策略policy存儲(chǔ)在ObjcAssociation里面(類(lèi)似于一個(gè)模型),這個(gè)模型就是ObjectAssociationMap的value瑞凑。

四末秃,關(guān)聯(lián)對(duì)象總結(jié)

  1. 關(guān)聯(lián)對(duì)象的值并不是存儲(chǔ)在被關(guān)聯(lián)對(duì)象本身內(nèi)存中的;
  1. 關(guān)聯(lián)對(duì)象的值存儲(chǔ)在全局統(tǒng)一的一個(gè)AssociationsManager中
  1. 刪除一個(gè)對(duì)象的屬性:設(shè)置關(guān)聯(lián)對(duì)象的值為nil

例如:

objc_setAssociatedObject(self, @selector(height), nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  1. 刪除一個(gè)對(duì)象:直接調(diào)用objc_removeAssociatedObjects(id _Nonnull object)

五籽御,回答文章開(kāi)頭的面試題

一练慕,Category能否添加成員變量?如果可以技掏,如何給Category添加成員變量铃将?
不能直接給Category添加成員變量,可以間接添加哑梳;可以通過(guò)runtime的關(guān)聯(lián)對(duì)象添加成員變量.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末劲阎,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子鸠真,更是在濱河造成了極大的恐慌悯仙,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,941評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件吠卷,死亡現(xiàn)場(chǎng)離奇詭異锡垄,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)祭隔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)货岭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人疾渴,你說(shuō)我怎么就攤上這事千贯。” “怎么了搞坝?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,345評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵搔谴,是天一觀(guān)的道長(zhǎng)。 經(jīng)常有香客問(wèn)我瞄沙,道長(zhǎng)己沛,這世上最難降的妖魔是什么慌核? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,851評(píng)論 1 295
  • 正文 為了忘掉前任距境,我火速辦了婚禮,結(jié)果婚禮上垮卓,老公的妹妹穿的比我還像新娘垫桂。我一直安慰自己,他們只是感情好粟按,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布诬滩。 她就那樣靜靜地躺著霹粥,像睡著了一般。 火紅的嫁衣襯著肌膚如雪疼鸟。 梳的紋絲不亂的頭發(fā)上后控,一...
    開(kāi)封第一講書(shū)人閱讀 51,688評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音空镜,去河邊找鬼浩淘。 笑死,一個(gè)胖子當(dāng)著我的面吹牛吴攒,可吹牛的內(nèi)容都是我干的张抄。 我是一名探鬼主播,決...
    沈念sama閱讀 40,414評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼洼怔,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼署惯!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起镣隶,我...
    開(kāi)封第一講書(shū)人閱讀 39,319評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤极谊,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后安岂,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體怀酷,經(jīng)...
    沈念sama閱讀 45,775評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年嗜闻,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了蜕依。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,096評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖戒傻,靈堂內(nèi)的尸體忽然破棺而出昼捍,到底是詐尸還是另有隱情,我是刑警寧澤檐束,帶...
    沈念sama閱讀 35,789評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站束倍,受9級(jí)特大地震影響被丧,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜绪妹,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評(píng)論 3 331
  • 文/蒙蒙 一甥桂、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧邮旷,春花似錦黄选、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,993評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)貌夕。三九已至,卻和暖如春民镜,著一層夾襖步出監(jiān)牢的瞬間啡专,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,107評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工制圈, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留植旧,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,308評(píng)論 3 372
  • 正文 我出身青樓离唐,卻偏偏與公主長(zhǎng)得像病附,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子亥鬓,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評(píng)論 2 355

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