通過探索Category底層原理回答以下問題
- Category是否可以添加方法诵原、屬性英妓、成員變量?Category是否可以遵守Protocol绍赛?
- Category的本質(zhì)是什么蔓纠,在底層是怎么存儲(chǔ)的?
- Category的實(shí)現(xiàn)原理是什么吗蚌,Catagory中的方法是如何調(diào)用到的腿倚?
- Category中是否有Load方法,load方法是什么時(shí)候調(diào)用的蚯妇?
- load敷燎、initialize的區(qū)別
Category可以直接添加 屬性、成員變量嗎侮措?
首先我們創(chuàng)建一個(gè)類MGCPerson懈叹,為該類添加一個(gè)分類MGCPerson (Sport),并嘗試為該分類添加實(shí)例方法分扎、類方法澄成、屬性、成員變量畏吓,觀察是否報(bào)錯(cuò)
創(chuàng)建MGCPerson
// MGCPerson.h
@interface MGCPerson : NSObject
@property (nonatomic, assign) NSInteger height;
@property (nonatomic, assign) NSInteger weight;
@property (nonatomic, copy) NSString *name;
@property (nonatomic, strong) NSDate *brithday;
- (void)life;
+ (void)life;
@end
// MGCPerson.m
@implementation MGCPerson
- (void)life
{
NSLog(@"MGCPerson : - (void)life");
}
+ (void)life
{
NSLog(@"MGCPerson : + (void)life");
}
@end
添加分類MGCPerson (Sport)墨状,并嘗試為該分類添加實(shí)例方法、類方法菲饼、屬性肾砂、成員變量
通過操作我們發(fā)現(xiàn),可以在Category中直接添加實(shí)例方法宏悦、類方法镐确、屬性以及準(zhǔn)守協(xié)議包吝,但是當(dāng)嘗試在Category中添加成員變量時(shí)會(huì)報(bào)錯(cuò)
Instance variables may not be placed in categories
,即不能直接在分類中添加成員變量源葫。接下來我們繼續(xù)探討Category的本質(zhì)诗越,分析為什么不能在Category中直接添加成員變量
Category的底層數(shù)據(jù)結(jié)構(gòu)
首先我們?yōu)镸GCPerson創(chuàng)建兩個(gè)分類MGCPerson (Sport)
、 MGCPerson (Eat)
MGCPerson (Sport)
// MGCPerson+Sport.h
@interface MGCPerson (Sport) <NSCopying, NSCoding>
@property (nonatomic, assign) NSInteger stepCount;
@property (nonatomic, copy) NSDate *sportStartTime;
@property (nonatomic, copy) NSDate *sportEndTime;
- (void)sport;
+ (void)sport;
- (void)run;
+ (void)run;
@end
// MGCPerson+Sport.m
@implementation MGCPerson (Sport)
- (void)sport
{
NSLog(@"MGCPerson (Sport) : - (void)sport");
}
+ (void)sport
{
NSLog(@"MGCPerson (Sport) : + (void)sport");
}
- (void)run
{
NSLog(@"MGCPerson (Sport) : - (void)run");
}
+ (void)run
{
NSLog(@"MGCPerson (Sport) : + (void)run");
}
- (void)life
{
NSLog(@"MGCPerson (Sport) : - (void)life");
}
+ (void)life
{
NSLog(@"MGCPerson (Sport) : + (void)life");
}
@end
MGCPerson (Eat)
// MGCPerson+Eat.h
@interface MGCPerson (Eat)
+ (void)eat;
- (void)eat;
@end
// MGCPerson+Eat.m
@implementation MGCPerson (Eat)
- (void)eat
{
NSLog(@"MGCPerson (Eat) : - (void)eat");
}
+ (void)eat
{
NSLog(@"MGCPerson (Eat) : + (void)eat");
}
- (void)life
{
NSLog(@"MGCPerson (Eat) : - (void)life");
}
+ (void)life
{
NSLog(@"MGCPerson (Eat) : + (void)life");
}
@end
我們知道OC是基于C/C++實(shí)現(xiàn)的息堂,接下來我們將通過命令xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc 文件名
將分類MGCPerson (Sport)
嚷狞、 MGCPerson (Eat)
轉(zhuǎn)化成.cpp
文件,窺探Category的底層實(shí)現(xiàn)
分析.cpp
文件思路
第一步: 因?yàn)槲覀兎治龅氖?code>.m轉(zhuǎn)化稱的.cpp
文件荣堰,所以我們首先嘗試在.cpp
文件中查找關(guān)鍵字@implementation MGCPerson (Sport)
床未,通過檢索,我們找到了對(duì)應(yīng)方法的實(shí)現(xiàn)
// @implementation MGCPerson (Sport)
static void _I_MGCPerson_Sport_sport(MGCPerson * self, SEL _cmd) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_gp_d5d9dd_x6kzfb5mn22zbqsg40000gn_T_MGCPerson_Sport_bea66e_mi_0);
}
static void _C_MGCPerson_Sport_sport(Class self, SEL _cmd) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_gp_d5d9dd_x6kzfb5mn22zbqsg40000gn_T_MGCPerson_Sport_bea66e_mi_1);
}
static void _I_MGCPerson_Sport_run(MGCPerson * self, SEL _cmd) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_gp_d5d9dd_x6kzfb5mn22zbqsg40000gn_T_MGCPerson_Sport_bea66e_mi_2);
}
static void _C_MGCPerson_Sport_run(Class self, SEL _cmd) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_gp_d5d9dd_x6kzfb5mn22zbqsg40000gn_T_MGCPerson_Sport_bea66e_mi_3);
}
static void _I_MGCPerson_Sport_life(MGCPerson * self, SEL _cmd) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_gp_d5d9dd_x6kzfb5mn22zbqsg40000gn_T_MGCPerson_Sport_bea66e_mi_4);
}
static void _C_MGCPerson_Sport_life(Class self, SEL _cmd) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_gp_d5d9dd_x6kzfb5mn22zbqsg40000gn_T_MGCPerson_Sport_bea66e_mi_5);
}
// @end
對(duì)函數(shù)名進(jìn)行簡(jiǎn)單分析振坚,得出命名規(guī)則
第二步: 檢索函數(shù)名_I_MGCPerson_Sport_sport
薇搁、_C_MGCPerson_Sport_sport
進(jìn)一步分析
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[3];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_MGCPerson_$_Sport __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
3,
{{(struct objc_selector *)"sport", "v16@0:8", (void *)_I_MGCPerson_Sport_sport},
{(struct objc_selector *)"run", "v16@0:8", (void *)_I_MGCPerson_Sport_run},
{(struct objc_selector *)"life", "v16@0:8", (void *)_I_MGCPerson_Sport_life}}
};
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[3];
} _OBJC_$_CATEGORY_CLASS_METHODS_MGCPerson_$_Sport __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
3,
{{(struct objc_selector *)"sport", "v16@0:8", (void *)_C_MGCPerson_Sport_sport},
{(struct objc_selector *)"run", "v16@0:8", (void *)_C_MGCPerson_Sport_run},
{(struct objc_selector *)"life", "v16@0:8", (void *)_C_MGCPerson_Sport_life}}
};
分析可知:上述代碼創(chuàng)建了_method_list_t
類型的結(jié)構(gòu)體變量_OBJC_$_CATEGORY_INSTANCE_METHODS_MGCPerson_$_Sport
和_OBJC_$_CATEGORY_CLASS_METHODS_MGCPerson_$_Sport
分別用于存儲(chǔ)實(shí)例方法列表和類方法列表。
struct _method_list_t {
unsigned int entsize; // sizeof(struct _objc_method) 方法所占內(nèi)存大小
unsigned int method_count; // 方法個(gè)數(shù)
struct _objc_method method_list[3]; // 方法列表
};
struct _objc_method {
struct objc_selector * _cmd; // SEL屡拨,可以理解為函數(shù)名字符串
const char *method_type; // 函數(shù)類型(包括返回值類型只酥、參數(shù)類型)
void *_imp; // 指向函數(shù)實(shí)現(xiàn)
};
這里簡(jiǎn)單說下_objc_method
中的method_type
褥实,詳細(xì)介紹后續(xù)在探究runtime消息機(jī)制時(shí)再繼續(xù)扒呀狼。method_type
其實(shí)可以看做用字符縮寫來表達(dá)的函數(shù)類型字符串,比如v@:i
就是返回類型為void
损离,第一個(gè)參數(shù)為id類型
哥艇,第二個(gè)參數(shù)為指針類型
,第三個(gè)參數(shù)為int類型
的函數(shù)僻澎,如- (void)addStepCount:(int)count
(我們知道iOS中方法調(diào)用會(huì)默認(rèn)傳入隱式參數(shù) 方法調(diào)用者:self 和 方法名:_cmd貌踏。這也是為什么我們可以在方法內(nèi)部訪問self
、cmd
的原因)
第三步:繼續(xù)檢索_OBJC_$_CATEGORY_INSTANCE_METHODS_MGCPerson_$_Sport
窟勃,繼續(xù)分析
static struct _category_t _OBJC_$_CATEGORY_MGCPerson_$_Sport __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"MGCPerson",
0, // &OBJC_CLASS_$_MGCPerson,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_MGCPerson_$_Sport,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_MGCPerson_$_Sport,
(const struct _protocol_list_t *)&_OBJC_CATEGORY_PROTOCOLS_$_MGCPerson_$_Sport,
(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_MGCPerson_$_Sport,
};
分析可知:上述代碼創(chuàng)建了一個(gè)_category_t
類型的結(jié)構(gòu)體變量 _OBJC_$_CATEGORY_MGCPerson_$_Sport
祖乳,并且傳入了類方法列表、實(shí)例方法列表秉氧、協(xié)議類別眷昆、屬性列表,具備了Category的所有信息汁咏,沒錯(cuò)Category在底層就是_category_t
類型亚斋,下邊我們檢索結(jié)構(gòu)體類型_category_t
,看下_category_t
的定義
struct _category_t {
const char *name; // 類名(MGCPerson)
struct _class_t *cls; // 類對(duì)象指針(指向MGCPerson類對(duì)象)
const struct _method_list_t *instance_methods; // 實(shí)例方法列表
const struct _method_list_t *class_methods; // 類方法列表
const struct _protocol_list_t *protocols; // 協(xié)議列表
const struct _prop_list_t *properties; // 屬性列表
};
到此我們已經(jīng)清楚的看到了Category在底層的存儲(chǔ)結(jié)構(gòu)攘滩,并且可以看到底層并沒有存儲(chǔ)成員變量帅刊,這也就是為什么直接添加成員變量會(huì)報(bào)錯(cuò)的原因。
上邊我們已經(jīng)分析了_category_t
及_method_list_t
漂问,下邊我們?cè)倏聪?code>_protocol_list_t和_prop_list_t
先看_protocol_list_t
赖瞒,同樣的在.cpp
文件中檢索_protocol_list_t
static struct /*_protocol_list_t*/ {
long protocol_count; // Note, this is 32/64 bit 協(xié)議個(gè)數(shù)
struct _protocol_t *super_protocols[2]; // 協(xié)議數(shù)組(2是數(shù)組長(zhǎng)度)
} _OBJC_CATEGORY_PROTOCOLS_$_MGCPerson_$_Sport __attribute__ ((used, section ("__DATA,__objc_const"))) = {
2,
&_OBJC_PROTOCOL_NSCopying, // 協(xié)議變量地址
&_OBJC_PROTOCOL_NSCoding // 協(xié)議變量地址
};
分析可知:上述代碼定義了_protocol_list_t
類型的結(jié)構(gòu)體變量_OBJC_CATEGORY_PROTOCOLS_$_MGCPerson_$_Sport
用于存儲(chǔ)協(xié)議列表
struct _protocol_t _OBJC_PROTOCOL_NSCopying __attribute__ ((used)) = {
0,
"NSCopying",
0,
(const struct method_list_t *)&_OBJC_PROTOCOL_INSTANCE_METHODS_NSCopying,
0,
0,
0,
0,
sizeof(_protocol_t),
0,
(const char **)&_OBJC_PROTOCOL_METHOD_TYPES_NSCopying
};
struct _protocol_t _OBJC_PROTOCOL_NSCoding __attribute__ ((used)) = {
0,
"NSCoding",
0,
(const struct method_list_t *)&_OBJC_PROTOCOL_INSTANCE_METHODS_NSCoding,
0,
0,
0,
0,
sizeof(_protocol_t),
0,
(const char **)&_OBJC_PROTOCOL_METHOD_TYPES_NSCoding
};
分析可知:上述代碼定義了兩個(gè)_protocol_t
類型的協(xié)議變量_OBJC_PROTOCOL_NSCopying
和_OBJC_PROTOCOL_NSCoding
struct _protocol_t {
void * isa; // NULL
const char *protocol_name; // 協(xié)議名稱
const struct _protocol_list_t * protocol_list; // super protocols 父協(xié)議
const struct method_list_t *instance_methods; // 協(xié)議中實(shí)例方法列表
const struct method_list_t *class_methods; // 協(xié)議中類方法列表
const struct method_list_t *optionalInstanceMethods;// 協(xié)議中可選實(shí)現(xiàn)的實(shí)例方法列表
const struct method_list_t *optionalClassMethods;// 協(xié)議中可選實(shí)現(xiàn)的類方法列表
const struct _prop_list_t * properties; // 協(xié)議中的屬性列表
const unsigned int size; // sizeof(struct _protocol_t) // 協(xié)議所占內(nèi)存大小
const unsigned int flags; // = 0
const char ** extendedMethodTypes;
};
再來看下_prop_list_t
static struct /*_prop_list_t*/ {
unsigned int entsize; // sizeof(struct _prop_t) 屬性數(shù)組所占內(nèi)存大小
unsigned int count_of_properties; // 屬性個(gè)數(shù)
struct _prop_t prop_list[3]; // 屬性數(shù)組
} _OBJC_$_PROP_LIST_MGCPerson_$_Sport __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_prop_t),
3,
{{"stepCount","Tq,N"},
{"sportStartTime","T@\"NSDate\",C,N"}, // 屬性名女揭、 類型名、copy栏饮、nonatomic
{"sportEndTime","T@\"NSDate\",C,N"}} // 屬性名田绑、 類型名、copy抡爹、nonatomic
};
分析可知:上述代碼創(chuàng)建了一個(gè)_prop_t
類型的結(jié)構(gòu)體變量_OBJC_$_PROP_LIST_MGCPerson_$_Sport
用于存放屬性列表
struct _prop_t {
const char *name; // 屬性名
const char *attributes; // 屬性描述掩驱,就是@property后邊的一些關(guān)鍵字
};
到此我們已經(jīng)了解了Category的底層數(shù)據(jù)結(jié)構(gòu),以及可以在Category中添方法冬竟、協(xié)議欧穴、屬性,但是不能添加成員變量泵殴,那么Category中的屬性和類中的屬性是否一樣的呢涮帘?如果不一樣,怎么實(shí)現(xiàn)和類中添加屬性一樣的效果呢笑诅?接下來我們將探討這個(gè)問題
Category中的屬性
我們已經(jīng)為MGCPerson
添加了height
调缨、weight
、name
吆你、brithday
屬性弦叶,為MGCPerson+Sport
添加了stepCount
、sportStartTime
妇多、sportEndTime
屬性
并且我們知道在類中添加一個(gè)屬性伤哺,系統(tǒng)為我們做了三件事
@property (nonatomic, copy) NSString *name;
- 創(chuàng)建了一個(gè)成員變量
_name
- 生成了
setter
、getter
方法的聲明 - 生成了
setter
者祖、getter
方法的實(shí)現(xiàn)
那么在Category添加屬性系統(tǒng)是否也會(huì)為我們做這三件事呢立莉?
-
Category添加屬性系統(tǒng)不會(huì)自動(dòng)添加成員變量
根據(jù)上邊的學(xué)習(xí)我們知道,_category_t 中并沒有存儲(chǔ)成員變量 -
Category添加屬性系統(tǒng)會(huì)生成
setter
七问、getter
方法的聲明
嘗試在MGCPerson+Sport.h
中直接敲對(duì)應(yīng)的setter
蜓耻、getter
,發(fā)現(xiàn)是有提示的械巡,說明會(huì)生成setter
刹淌、getter
方法的聲明 -
Category添加屬性系統(tǒng)不會(huì)生成
setter
、getter
方法的實(shí)現(xiàn)
下邊我們比下MGCPerson.cpp
和MGCPerson+Sport.cppp
文件
// @implementation MGCPerson
static void _I_MGCPerson_life(MGCPerson * self, SEL _cmd) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_gp_d5d9dd_x6kzfb5mn22zbqsg40000gn_T_MGCPerson_d3d3d6_mi_0);
}
static void _C_MGCPerson_life(Class self, SEL _cmd) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_gp_d5d9dd_x6kzfb5mn22zbqsg40000gn_T_MGCPerson_d3d3d6_mi_1);
}
static NSInteger _I_MGCPerson_height(MGCPerson * self, SEL _cmd) { return (*(NSInteger *)((char *)self + OBJC_IVAR_$_MGCPerson$_height)); }
static void _I_MGCPerson_setHeight_(MGCPerson * self, SEL _cmd, NSInteger height) { (*(NSInteger *)((char *)self + OBJC_IVAR_$_MGCPerson$_height)) = height; }
static NSInteger _I_MGCPerson_weight(MGCPerson * self, SEL _cmd) { return (*(NSInteger *)((char *)self + OBJC_IVAR_$_MGCPerson$_weight)); }
static void _I_MGCPerson_setWeight_(MGCPerson * self, SEL _cmd, NSInteger weight) { (*(NSInteger *)((char *)self + OBJC_IVAR_$_MGCPerson$_weight)) = weight; }
static NSString * _Nonnull _I_MGCPerson_name(MGCPerson * self, SEL _cmd) { return (*(NSString * _Nonnull *)((char *)self + OBJC_IVAR_$_MGCPerson$_name)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);
static void _I_MGCPerson_setName_(MGCPerson * self, SEL _cmd, NSString * _Nonnull name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct MGCPerson, _name), (id)name, 0, 1); }
static NSDate * _Nonnull _I_MGCPerson_brithday(MGCPerson * self, SEL _cmd) { return (*(NSDate * _Nonnull *)((char *)self + OBJC_IVAR_$_MGCPerson$_brithday)); }
static void _I_MGCPerson_setBrithday_(MGCPerson * self, SEL _cmd, NSDate * _Nonnull brithday) { (*(NSDate * _Nonnull *)((char *)self + OBJC_IVAR_$_MGCPerson$_brithday)) = brithday; }
// @end
// @implementation MGCPerson (Sport)
static void _I_MGCPerson_Sport_sport(MGCPerson * self, SEL _cmd) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_gp_d5d9dd_x6kzfb5mn22zbqsg40000gn_T_MGCPerson_Sport_bea66e_mi_0);
}
static void _C_MGCPerson_Sport_sport(Class self, SEL _cmd) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_gp_d5d9dd_x6kzfb5mn22zbqsg40000gn_T_MGCPerson_Sport_bea66e_mi_1);
}
static void _I_MGCPerson_Sport_run(MGCPerson * self, SEL _cmd) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_gp_d5d9dd_x6kzfb5mn22zbqsg40000gn_T_MGCPerson_Sport_bea66e_mi_2);
}
static void _C_MGCPerson_Sport_run(Class self, SEL _cmd) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_gp_d5d9dd_x6kzfb5mn22zbqsg40000gn_T_MGCPerson_Sport_bea66e_mi_3);
}
static void _I_MGCPerson_Sport_life(MGCPerson * self, SEL _cmd) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_gp_d5d9dd_x6kzfb5mn22zbqsg40000gn_T_MGCPerson_Sport_bea66e_mi_4);
}
static void _C_MGCPerson_Sport_life(Class self, SEL _cmd) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_gp_d5d9dd_x6kzfb5mn22zbqsg40000gn_T_MGCPerson_Sport_bea66e_mi_5);
}
// @end
可以看到坟比,MGCPerson
自動(dòng)生成了對(duì)應(yīng)屬性的setter
芦鳍、getter
的實(shí)現(xiàn)方法,但是MGCPerson+Sport
卻沒有生成對(duì)應(yīng)屬性的setter
葛账、getter
的實(shí)現(xiàn)方法柠衅,綜上所述在Category添加屬性系統(tǒng)僅僅只生成了setter
、getter
方法的聲明
如何使Category中的屬性與類中的屬性具備同樣的效果(關(guān)聯(lián)對(duì)象)
通過上邊的學(xué)習(xí)籍琳,我們知道想達(dá)到與類添加屬性一樣的效果菲宴,我們需要做兩件事
- 添加一個(gè)存儲(chǔ)屬性值的變量
- 實(shí)現(xiàn)
setter
贷祈、getter
方法
可以通過runtime中的關(guān)聯(lián)對(duì)象方法來實(shí)現(xiàn)
/ **
*使用給定的鍵和關(guān)聯(lián)策略為給定的對(duì)象設(shè)置關(guān)聯(lián)值。
*
* @param object 關(guān)聯(lián)的源對(duì)象喝峦。
* @param key 關(guān)聯(lián)的鍵势誊。
* @param value 與對(duì)象的鍵相關(guān)聯(lián)的值。 傳遞nil清除現(xiàn)有關(guān)聯(lián)谣蠢。
* @param policy 關(guān)聯(lián)的策略
*
* /
objc_setAssociatedObject(<#id _Nonnull object#>, <#const void * _Nonnull key#>, <#id _Nullable value#>, <#objc_AssociationPolicy policy#>)
// 關(guān)聯(lián)策略粟耻,和@property后的關(guān)鍵字對(duì)應(yīng)
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
};
- (void)setSportEndTime:(NSDate *)sportEndTime
{
objc_setAssociatedObject(self, @selector(sportEndTime), sportEndTime, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSDate *)sportEndTime
{
return objc_getAssociatedObject(self, _cmd);
}
系統(tǒng)是如何管理關(guān)聯(lián)對(duì)象的
去官網(wǎng)下載runtime源碼,搜索objc_setAssociatedObject
方法眉踱,這里不做過多分析挤忙,簡(jiǎn)單說下結(jié)論,后續(xù)會(huì)單開一篇扒關(guān)聯(lián)對(duì)象的實(shí)現(xiàn)原理谈喳。
runtime用四個(gè)類來管理關(guān)聯(lián)對(duì)象册烈,AssociationsManager
、AssociationsHashMap
婿禽、AssociationsMap
赏僧、ObjectAssociation
,四個(gè)類之間的關(guān)系如下圖
- 對(duì)象并不是存儲(chǔ)在被關(guān)聯(lián)對(duì)象本身內(nèi)存中
- 關(guān)聯(lián)對(duì)象存儲(chǔ)在全局的統(tǒng)一的一個(gè)AssociationsManager中
- 設(shè)置關(guān)聯(lián)對(duì)象為nil扭倾,就相當(dāng)于是移除關(guān)聯(lián)對(duì)象
小尾巴
到此我們已經(jīng)解決了開篇中的第1淀零、2個(gè)問題,后續(xù)問題見iOS底層原理 - Category實(shí)現(xiàn)原理(二)