一队萤、isKindOfClass和isMemberOfClass
題目:
有一道經(jīng)典面試題關于isKindOfClass和isMemberOfClass
代碼:
BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]]; // 1
BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]]; // 0
BOOL re3 = [(id)[LGPerson class] isKindOfClass:[LGPerson class]]; // 0
BOOL re4 = [(id)[LGPerson class] isMemberOfClass:[LGPerson class]]; // 0
NSLog(@" re1 :%hhd\n re2 :%hhd\n re3 :%hhd\n re4 :%hhd\n",re1,re2,re3,re4);
BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]]; // 1
BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]]; // 1
BOOL re7 = [(id)[LGPerson alloc] isKindOfClass:[LGPerson class]]; // 1
BOOL re8 = [(id)[LGPerson alloc] isMemberOfClass:[LGPerson class]]; // 1
NSLog(@" re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);
輸出結果:
2019-12-31 14:49:22.734091+0800 LGTest[35237:2807868]
re1 :1
re2 :0
re3 :0
re4 :0
2019-12-31 14:49:22.735580+0800 LGTest[35237:2807868]
re5 :1
re6 :1
re7 :1
re8 :1
為什么結果是這樣呢?
答案:
我們先放一個isa的指針圖:
再打開一份objc的源碼矫钓,來看一下對應方法里面的實現(xiàn)
- 1浮禾、我們先看一下類的class方法
+ (Class)class {
return self;
}
- 2、看一下類的isKindOfClass的實現(xiàn)
+ (BOOL)isKindOfClass:(Class)cls {
//
for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
再看一下object_getClass的源碼
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}
我們發(fā)現(xiàn)isKindOfClass是循環(huán)不斷獲取self的isa指針以及父類的isa指針指向和cls做對比份汗,通過上面isa的指向圖盈电,我們對上面判斷一一解釋下:
BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]]; // 1
因為NSObject的isa指向NSObject的元類,先拿到NSObject的元類跟NSObject比杯活,不通過匆帚,而NSObject元類的isa指向的是NSObject,然后跟NSObject對比旁钧,所以結果是YES
BOOL re3 = [(id)[LGPerson class] isKindOfClass:[LGPerson class]]; // 0
而LGPerson的isa指向依次是LGPerson的元類 ---> NSObject的元類 ---> NSObject --- > nil,然后和LGPerson進行對比吸重,沒有匹配的互拾,所以結果是NO
- 3、再看一下類的isMemberOfClass的實現(xiàn)
+ (BOOL)isMemberOfClass:(Class)cls {
return object_getClass((id)self) == cls;
}
我們發(fā)現(xiàn)isMemberOfClass僅僅是拿到當前self的isa指針指向和cls對比嚎幸,然后我們分析測試代碼邏輯:
BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]]; // 0
NSObject的元類和NSObject不匹配颜矿,所以不成立
BOOL re4 = [(id)[LGPerson class] isMemberOfClass:[LGPerson class]]; // 0
LGPerson的元類和LGPerson不匹配,所以不成立
- 4嫉晶、再看一下實例方法的isKindOfClass方法和isMemberOfClass的實現(xiàn)
- (BOOL)isKindOfClass:(Class)cls {
// 類 - NSObject 類 vs 父類 nil
for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}
- (Class)class {
return object_getClass(self);
}
這里有一個iOS交流圈:891 488 181 分享BAT,阿里面試題骑疆、面試經(jīng)驗,討論技術替废,有興趣的可以進來了解箍铭。
我們可以發(fā)現(xiàn)對于對象方法,只是拿到對象的isa指向和相應的類對比椎镣,而對象的isa指向的都是相應的類诈火,所以下面四個輸出結果都是YES。
二状答、[super class]和[self class]
題目:
我們創(chuàng)建一個集成LGPerson的類LGStudent的類冷守,然后在LGStudent的實例方法里面寫下面代碼,然后調(diào)用該對象方法:
-(void)testSuperClass{
NSLog(@"%@",NSStringFromClass([self class]));
NSLog(@"%@",NSStringFromClass([super class]));
}
輸出:
2020-01-16 10:36:23.651909+0800 LGTest[18422:366866] LGStudent
2020-01-16 10:36:23.652760+0800 LGTest[18422:366866] LGStudent
這是為什么呢惊科,[self class]我們都能理解是LGStudent拍摇,但是[super class]為什么也是LGStudent呢,不應該是LGPerson嗎译断,下面我們來探索下:
答案:
1授翻、匯編分析法
我們發(fā)現(xiàn)[super class]是通過objc_msgSendSuper2進行發(fā)送消息的堪唐,而不是通過objc_msgSend發(fā)送消息的,我們再到objc源碼中去找一下objc_msgSendSuper2的實現(xiàn)
/********************************************************************
* id objc_msgSendSuper2(struct objc_super *super, SEL op, ...)
*
* struct objc_super {
* id receiver;
* Class cls; // SUBCLASS of the class to search
* }
********************************************************************/
ENTRY _objc_msgSendSuper2
ldr r9, [r0, #CLASS] // class = struct super->class
ldr r9, [r9, #SUPERCLASS] // class = class->superclass
CacheLookup NORMAL
// cache hit, IMP in r12, eq already set for nonstret forwarding
ldr r0, [r0, #RECEIVER] // load real receiver
bx r12 // call imp
CacheLookup2 NORMAL
// cache miss
ldr r9, [r0, #CLASS] // class = struct super->class
ldr r9, [r9, #SUPERCLASS] // class = class->superclass
ldr r0, [r0, #RECEIVER] // load real receiver
b __objc_msgSend_uncached
END_ENTRY _objc_msgSendSuper2
我們最終在匯編地方找到了實現(xiàn)翎蹈,并且發(fā)現(xiàn)_objc_msgSendSuper2的參數(shù)分別為objc_super淮菠、SEL等等,其中objc_super是消息接受者荤堪,并且它是一個結構體:
* struct objc_super {
* id receiver;
* Class cls; // SUBCLASS of the class to search
* }
我們知道receiver是self合陵,cls是self的父類,_objc_msgSendSuper2其實就從self的父類開始查找方法澄阳,但是消息接受者還是self本身拥知,也就類似是讓self去調(diào)父類的class方法,所以返回的都是LGStudent
2碎赢、hook分析法:
我們創(chuàng)建一個NSObject的分類低剔,然后在里面hook一下class方法
+ (void)load{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[LGRuntimeTool lg_bestMethodSwizzlingWithClass:self oriSEL:@selector(class) swizzledSEL:@selector(lg_class)];
});
}
- (Class)lg_class{
NSLog(@"來了,老弟");
return [self lg_class]; // sel -> imp(class)
}
我們在lg_class里面打一個斷點,通過lldb來看一下調(diào)進來的self是什么: 打印結果:
2020-01-16 11:04:30.876482+0800 LGTest[19658:395083] 來了,老弟
(lldb) p self
(LGStudent *) $0 = 0x00000001022327c0
2020-01-16 11:04:54.903791+0800 LGTest[19658:395083] 來了,老弟
(lldb) p self
(NSTaggedPointerString *) $1 = 0xd76f961d90151cc3 @"LGStudent"
2020-01-16 11:05:07.057101+0800 LGTest[19658:395083] LGStudent
我們發(fā)現(xiàn)調(diào)進來的self都是LGStudent,所以也驗證了[super class]的調(diào)用者還是self本身
三襟齿、weak和strong底層原理
問題:
__weak我們在項目中經(jīng)常用于打破循環(huán)引用姻锁,但為什么weak可以打破循環(huán)引用呢?strong又是怎么回事呢猜欺?
答案:
weak
我們在objc源碼中的main方法中寫上下面這句代碼位隶,打上斷點并打開匯編調(diào)試:
LGPerson __weak *objc = object;
然后我們發(fā)現(xiàn)在此處調(diào)用了objc_initWeak方法,我們再點擊進去:
/**
* Initialize a fresh weak pointer to some object location.
* It would be used for code like:
*
* (The nil case)
* __weak id weakPtr;
* (The non-nil case)
* NSObject *o = ...;
* __weak id weakPtr = o;
*
* This function IS NOT thread-safe with respect to concurrent
* modifications to the weak variable. (Concurrent weak clear is safe.)
*
* @param location Address of __weak ptr.
* @param newObj Object ptr.
*/
id
objc_initWeak(id *location, id newObj)
{
if (!newObj) {
*location = nil;
return nil;
}
return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
(location, (objc_object*)newObj);
}
根據(jù)注釋我們知道weak的使用方法开皿,并且介紹了該方法是用來初始化對象弱指針的涧黄,并且是線程不安全的,根據(jù)代碼進入到了storeWeak函數(shù)副瀑,我們再進入到storeWeak里面看看弓熏。
源碼太長就先不放了恋谭,根據(jù)源碼分析糠睡,前部分都是對表進行判斷的,并且我們知道弱引用指針是存在一個叫SideTable的表中疚颊,再往下我們發(fā)現(xiàn)如果沒表就走weak_register_no_lock函數(shù)狈孔,看名字知道應該是注冊弱引用指針的方法,如果有就走weak_unregister_no_lock方法
我們再進入到weak_register_no_lock方法里:
id
weak_register_no_lock(weak_table_t *weak_table, id referent_id,
id *referrer_id, bool crashIfDeallocating)
{
objc_object *referent = (objc_object *)referent_id;
objc_object **referrer = (objc_object **)referrer_id;
if (!referent || referent->isTaggedPointer()) return referent_id;
// ensure that the referenced object is viable
bool deallocating;//判斷該對象是否在dealloc
if (!referent->ISA()->hasCustomRR()) {
deallocating = referent->rootIsDeallocating();
}else {
BOOL (*allowsWeakReference)(objc_object *, SEL) =
(BOOL(*)(objc_object *, SEL))
object_getMethodImplementation((id)referent,
SEL_allowsWeakReference);
if ((IMP)allowsWeakReference == _objc_msgForward) {
return nil;
}
deallocating =
! (*allowsWeakReference)(referent, SEL_allowsWeakReference);
}
if (deallocating) {
if (crashIfDeallocating) {
_objc_fatal("Cannot form weak reference to instance (%p) of "
"class %s. It is possible that this object was "
"over-released, or is in the process of deallocation.",
(void*)referent, object_getClassName((id)referent));
} else {
return nil;
}
}
// now remember it and where it is being stored
weak_entry_t *entry;//判斷表里有沒有這個對象的子表材义,如果有就從weak_table中取出weak_entry_t然后將弱指針插入到weak_entry_t中
if ((entry = weak_entry_for_referent(weak_table, referent))) {
append_referrer(entry, referrer);
}
else { //如果沒有就創(chuàng)建一個weak_entry_t均抽,再將這個weak_entry_t插入到weak_table中去
// 創(chuàng)建了這個weak_entry_t 再插入到weak_table
weak_entry_t new_entry(referent, referrer);
weak_grow_maybe(weak_table);
weak_entry_insert(weak_table, &new_entry);
}
// Do not set *referrer. objc_storeWeak() requires that the
// value not change.
return referent_id;
}
從上我們會發(fā)現(xiàn)weak指針在創(chuàng)建的時候并沒有調(diào)用retain操作,并且會將weak指針存儲在SideTable的weak_table中其掂,然后每個對象在weak_table里面都有一個對應的weak_entry_t油挥,每個weak_entry_t里面可以放多個弱指針
strong
有了weak我們再看看strong是什么情況呢? 我們依然打開匯編調(diào)試款熬,然后將__weak改成__strong然后運行
LGPerson __strong *objc = object;
我們發(fā)現(xiàn)此處調(diào)用的是objc_retain深寥,command+點擊,進不去贤牛,我們就在OBJC源碼里面搜惋鹅,也搜不到,怎么辦呢殉簸,考慮到匯編一般會在函數(shù)前面添加闰集,我們?nèi)サ鬫再次搜索,然后我們找到了objc_retain函數(shù)
id
objc_retain(id obj)
{
if (!obj) return obj;
if (obj->isTaggedPointer()) return obj;
return obj->retain();
}
然后進入到retaun函數(shù)
inline id
objc_object::retain()
{
assert(!isTaggedPointer());
if (fastpath(!ISA()->hasCustomRR())) {
return rootRetain();
}
return ((id(*)(objc_object *, SEL))objc_msgSend)(this, SEL_retain);
}
發(fā)現(xiàn)是通過objc_msgSend發(fā)送了SEL_retain消息般卑,然后讓引用計數(shù)器+1
四武鲁、runtime的應用以及注意點
問題:
我們經(jīng)常在項目中用的runtime,并且用的最多的是交換方法蝠检,在交換方法中有哪些注意事項呢:
答案:
1沐鼠、NSArray 類簇,
類簇實際上是Foundation framework框架下的一種設計模式蝇率,它管理了一組隱藏在公共接口下的私有類迟杂,
所以涉及到類簇的類刽沾,NSDictionary、NSArray排拷、侧漓,本身類并不是NSArray等,這個需要去確定該類是否是類簇监氢,然后在確定真正的類是什么布蔗,然后對真正的類進行交換才行
2、交換的方法是父類的方法
如果交換的方法是父類的方法浪腐,就會導致當父類調(diào)用該方法時候報錯纵揍,因為父類沒有子類的方法。
解決方法就是:先嘗試給交換的類添加要交換的方法议街,如果添加成功泽谨,說明自己沒有這個方法,那么就對該類做替換操作特漩,如果添加失敗說明自己有這個方法吧雹,那么就直接做交換操作。 代碼:
+ (void)lg_betterMethodSwizzlingWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL{
if (!cls) NSLog(@"傳入的交換類不能為空");
Method oriMethod = class_getInstanceMethod(cls, oriSEL);
Method swiMethod = class_getInstanceMethod(cls, swizzledSEL);
//方式一:
//給cls添加oriSEL方法涂身,確保cls有oriSEL方法
class_addMethod(cls, oriSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
//取到cls的oriSEL的Method雄卷,因為上面oriMethod可能是cls父類的,交換的話可能會導致父類找不到swizzledSEL方法
Method oriMethod1 = class_getInstanceMethod(cls, oriSEL);
//交換方法
method_exchangeImplementations(oriMethod1, swiMethod);
/**************************************************************/
//方式二:
//嘗試添加蛤售,如果添加成功丁鹉,說明自己沒有這個方法,那么就對該類做替換操作悴能,因為此處給oriSEL方法添加的方法指針是swiMethod的方法指針揣钦,那么swizzledSEL的指針就要改成oriSEL的指針
//如果添加失敗說明自己有這個方法,那么就直接做交換操作
// BOOL isSuccess = class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(oriMethod));
//
// if (isSuccess) {// 自己沒有 - 交換 - 沒有父類進行處理 (重寫一個)
// class_replaceMethod(cls, swizzledSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
//
// }else{ // 自己有就做交換操作
// method_exchangeImplementations(oriMethod, swiMethod);
// }
}
本質(zhì)就是先給cls添加oriSEL方法搜骡,確保cls有了oriSEL方法后再交換拂盯,這樣就不會交換到cls父類的方法
3、交換的方法不存在
假如交換的方法不存在记靡,就會導致交換失敗谈竿,那么就要在上面代碼中單獨處理下單獨處理下:
Method oriMethod = class_getInstanceMethod(cls, oriSEL);
Method swiMethod = class_getInstanceMethod(cls, swizzledSEL);
if (!oriMethod) {
// 在oriMethod為nil時,替換后將swizzledSEL復制一個不做任何事的空實現(xiàn),代碼如下:
class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod));
method_setImplementation(swiMethod, imp_implementationWithBlock(^(id self, SEL _cmd){ }));
}
當方法不存在時候就需要單獨給cls添加一個方法實現(xiàn)
五摸吠、內(nèi)存偏移面試題
問題:
我們創(chuàng)建一個有saySomething實例方法的LGPerson的類空凸,問下面代碼能執(zhí)行嗎?
id pcls = [LGPerson class];
void *pp= &pcls;
[(__bridge id)pp saySomething];
//p -> LGPerson 實例對象
LGPerson *p = [LGPerson alloc];
[p saySomething];
答案:
答案是可以執(zhí)行的寸痢。為什么呢?我們來分析下:
- 1呀洲、首先對象的本質(zhì)是個結構體,并且第一個元素是isa,isa指向的是對象的類對象
- 2道逗、我們知道指針指向的對象的地址其實就是對象的首地址兵罢,所以p指向的是LGPerson對象的首地址isa
- 3、而isa指向的是LGPerson的類對象滓窍,那么對象指針就形成了這樣的指向關系:p ---> 對象 ---> LGPerson類對象
- 4卖词、再看上面的pcls:pcls指向的是LGPerson的類對象,pp又指向的是pcls吏夯,這樣也形成了一個指向關系:pp ---> pcls ---> LGPerson類對象
- 5此蜈、兩者對比起來,p和pp性質(zhì)就是一樣的了噪生,所以上面兩個都能執(zhí)行
疑問:但是對象是個結構體可以執(zhí)行方法裆赵,&pcls只是個地址為啥也能執(zhí)行方法
我們再擴展一下: 我們將saySomething重寫一下,并且給LGPerson增加一個NSString屬性name
- (void)saySomething{
NSLog(@"NB %s - %@",__func__,self.name);
}
在執(zhí)行一下跺嗽,看一下打诱绞凇:
2020-01-16 21:07:16.278767+0800 LGTest[50715:802279] NB -[LGPerson saySomething] - <ViewController: 0x7f84047063b0>
這又是為啥呢,我們猜測一下這個地方是野指針抛蚁,正好ViewController對象在那一塊陈醒,但是我們多次運行測試后結果卻一樣惕橙,我們來分析一下:
我們做一個測試瞧甩,在方法中寫下如下代碼:
int a = 1;
int b = 2;
int c = 3;
int d = 4;
NSLog(@"a = %p\nb = %p\nc = %p\nd = %p\n",&a,&b,&c,&d);
得到打印結果
a = 0x7ffee837d19c
b = 0x7ffee837d198
c = 0x7ffee837d194
d = 0x7ffee837d190
我們發(fā)現(xiàn)a、b弥鹦、c肚逸、d地址是連續(xù)的,且abcd都是臨時變量彬坏,變量是以棧的形式存儲朦促,
- 1、我們知道OC對象本質(zhì)是結構體栓始,里面第一個元素是isa务冕,然后下面元素依次是對象的屬性
- 2、當我們只有一個屬性時候幻赚,對象訪問屬性其實就是將對象的指針下移屬性大小的位置
- 3禀忆、那么上面那只方式其實訪問的就是pp下移8個字節(jié)的位置的數(shù)據(jù)
- 4、每個方法都有兩個隱藏參數(shù)super和self落恼,所以在方法中臨時變量順序是super箩退、self、pcls佳谦、pp戴涝,pp指向的是pcls
- 5、當我們用pp獲取name的時候,本質(zhì)上就是得到pcls指針然后往下移name的大小的位置啥刻,然后讀取值奸鸯,name大小是8字節(jié),pcls大小也是8字節(jié)可帽,所以移完之后正好指向了self
這就是為啥打印出<ViewController: 0x7f84047063b0>的原因府喳。
我們可以再做一個實驗,在代碼前加上一個NSString臨時變量
NSString *tem = @"KC";
id pcls = [LGPerson class];
void *pp= &pcls;
[(__bridge id)pp saySomething];
再執(zhí)行蘑拯,打佣勐:
2020-01-16 21:43:05.084478+0800 LGTest[52497:844500] NB -[LGPerson saySomething] - KC
更印證了我們的結果
五、關聯(lián)對象的原理
問題:
分類中如何創(chuàng)建屬性申窘?
答案:
在分類中創(chuàng)建屬性弯蚜,我們一般會寫上一個屬性,然后實現(xiàn)該屬性的set和get方法剃法,再關聯(lián)對象碎捺,這是為什么呢,我們來一步步分析:
1贷洲、創(chuàng)建屬性
在分類中創(chuàng)建了一個屬性收厨,會在rw中屬性列表中有數(shù)據(jù),然后有了set和get方法优构,但是該屬性沒有成員變量诵叁,需要重寫該屬性的set/get方法來保存屬性值。
2钦椭、重寫set/get方法拧额,關聯(lián)對象
首先我們來看下set方法
-(void)setCate_name:(NSString *)cate_name{
/**
參數(shù)一:id object : 給哪個對象添加屬性,這里要給自己添加屬性彪腔,用self侥锦。
參數(shù)二:void * == id key : 屬性名,根據(jù)key獲取關聯(lián)對象的屬性的值德挣,在objc_getAssociatedObject中通過次key獲得屬性的值并返回恭垦。
參數(shù)三:id value : 關聯(lián)的值,也就是set方法傳入的值給屬性去保存格嗅。
參數(shù)四:objc_AssociationPolicy policy : 策略番挺,屬性以什么形式保存。
*/
objc_setAssociatedObject(self, @"name",cate_name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
然后我們再看看objc_setAssociatedObject方法:
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) {
_object_set_associative_reference(object, (void *)key, value, policy);
}
這里我們會發(fā)現(xiàn)吗浩,蘋果對外接口建芙,一般都有一個加_的對內(nèi)接口與之對應,這是蘋果為了解耦合懂扼,即使底層內(nèi)部實現(xiàn)了也不會影響到對外接口禁荸,我們再看一下_object_set_associative_reference的實現(xiàn):
void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
// This code used to work when nil was passed for object and key. Some code
// probably relies on that to not crash. Check and handle it explicitly.
// rdar://problem/44094390
if (!object && !value) return;
assert(object);
if (object->getIsa()->forbidsAssociatedObjects())
_objc_fatal("objc_setAssociatedObject called on instance (%p) of class %s which does not allow associated objects", object, object_getClassName(object));
// retain the new value (if any) outside the lock.
// 在鎖之外保留新值(如果有)右蒲。
ObjcAssociation old_association(0, nil);
// acquireValue會對retain和copy進行操作,
id new_value = value ? acquireValue(value, policy) : nil;
{
// 關聯(lián)對象的管理類
AssociationsManager manager;
// 獲取關聯(lián)的 HashMap -> 存儲當前關聯(lián)對象
AssociationsHashMap &associations(manager.associations());
// 對當前的對象的地址做按位去反操作 - 就是 HashMap 的key (哈希函數(shù))
disguised_ptr_t disguised_object = DISGUISE(object);
if (new_value) {
// break any existing association.
// 獲取 AssociationsHashMap 的迭代器 - (對象的) 進行遍歷
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
// secondary table exists
ObjectAssociationMap *refs = i->second;
// 根據(jù)key去獲取關聯(lián)屬性的迭代器
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) {
old_association = j->second;
// 替換設置新值
j->second = ObjcAssociation(policy, new_value);
} else {
// 到最后了 - 直接設置新值
(*refs)[key] = ObjcAssociation(policy, new_value);
}
} else {
// create the new association (first time).
// 如果AssociationsHashMap從沒有對象的關聯(lián)信息表赶熟,
// 那么就創(chuàng)建一個map并通過傳入的key把value存進去
ObjectAssociationMap *refs = new ObjectAssociationMap;
associations[disguised_object] = refs;
(*refs)[key] = ObjcAssociation(policy, new_value);
object->setHasAssociatedObjects();
}
} else {
// setting the association to nil breaks the association.
// 如果傳入的value是nil瑰妄,并且之前使用相同的key存儲過關聯(lián)對象,
// 那么就把這個關聯(lián)的value移除(這也是為什么傳入nil對象能夠把對象的關聯(lián)value移除)
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
ObjectAssociationMap *refs = i->second;
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) {
old_association = j->second;
refs->erase(j);
}
}
}
}
// release the old value (outside of the lock).
// 最后把之前使用傳入的這個key存儲的關聯(lián)的value釋放(OBJC_ASSOCIATION_SETTER_RETAIN策略存儲的)
if (old_association.hasValue()) ReleaseValue()(old_association);
}
從上面我們梳屢一下邏輯:
- 1、先對傳進來的值做下retain或者copy處理得到new_value
- 2、再獲取到管理所有關聯(lián)對象的哈希map總表的管理者AssociationsManager毅臊,然后拿到哈希map總表AssociationsHashMap
- 3、對關聯(lián)對象的地址進行取反操作得到哈希表對應的下標index(其實disguised_ptr_t就是一個long類型的)
- 4竹宋、如果得到的new_value不為空的話,就拿到總表的迭代器通過拿到的下標index進行遍歷查找
- 5地技、如果找到管理對象的關聯(lián)屬性哈希map表蜈七,然后再通過key去遍歷取值,
- 如果取到了莫矗,就先把新值設置到key上飒硅,再將舊值釋放掉
- 如果沒取到,就直接將新值設置在key上
- 6作谚、如果沒找到關聯(lián)對象的關聯(lián)屬性哈希map表三娩,就創(chuàng)建一個表,然后將新值設置在key上
- 7妹懒、如果得到的new_value為空的話雀监,就嘗試取值,取到了的話就將key對應的值置為nil彬伦,如果取不到就不做處理
我們再看一下objc_getAssociatedObject:
id objc_getAssociatedObject(id object, const void *key) {
return _object_get_associative_reference(object, (void *)key);
}
id _object_get_associative_reference(id object, void *key) {
id value = nil;
uintptr_t policy = OBJC_ASSOCIATION_ASSIGN;
{
// 關聯(lián)對象的管理類
AssociationsManager manager;
AssociationsHashMap &associations(manager.associations());
// 生成偽裝地址滔悉。處理參數(shù) object 地址
disguised_ptr_t disguised_object = DISGUISE(object);
// 所有對象的額迭代器
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
ObjectAssociationMap *refs = i->second;
// 內(nèi)部對象的迭代器
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) {
// 找到 - 把值和策略讀取出來
ObjcAssociation &entry = j->second;
value = entry.value();
policy = entry.policy();
// OBJC_ASSOCIATION_GETTER_RETAIN - 就會持有一下
if (policy & OBJC_ASSOCIATION_GETTER_RETAIN) {
objc_retain(value);
}
}
}
}
if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {
objc_autorelease(value);
}
return value;
}
發(fā)現(xiàn)流程跟objc_setAssociatedObject反過來而已~
作者:海浪寶寶
鏈接:https://juejin.cn/post/6844904049481875470