廢話(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)
這是從runtime源碼里面找到的對(duì)象數(shù)據(jù)結(jié)構(gòu),可以看出涛贯,是可以存儲(chǔ)在成員變量的诽嘉。
2. 分類(lèi)底層數(shù)據(jù)結(jié)構(gòu)
這是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)系如下:
2. 源碼查找流程
3. 核心代碼解讀
直接上兩張我總結(jié)的圖:
還是簡(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é)
- 關(guān)聯(lián)對(duì)象的值并不是存儲(chǔ)在被關(guān)聯(lián)對(duì)象本身內(nèi)存中的;
- 關(guān)聯(lián)對(duì)象的值存儲(chǔ)在全局統(tǒng)一的一個(gè)AssociationsManager中
- 刪除一個(gè)對(duì)象的屬性:設(shè)置關(guān)聯(lián)對(duì)象的值為nil
例如:
objc_setAssociatedObject(self, @selector(height), nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
- 刪除一個(gè)對(duì)象:直接調(diào)用objc_removeAssociatedObjects(id _Nonnull object)
五籽御,回答文章開(kāi)頭的面試題
一练慕,Category能否添加成員變量?如果可以技掏,如何給Category添加成員變量铃将?
不能直接給Category添加成員變量,可以間接添加哑梳;可以通過(guò)runtime的關(guān)聯(lián)對(duì)象添加成員變量.