木葉大旋風(fēng)之橫掃面試(iOS面試題系列)

一、isKindOfClass和isMemberOfClass

題目:

有一道經(jīng)典面試題關(guān)于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);

輸出結(jié)果:

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

為什么結(jié)果是這樣呢?

答案:

我們先放一個(gè)isa的指針圖:

再打開(kāi)一份objc的源碼,來(lái)看一下對(duì)應(yīng)方法里面的實(shí)現(xiàn)

  • 1、我們先看一下類(lèi)的class方法
+ (Class)class {
    return self;
}
  • 2、看一下類(lèi)的isKindOfClass的實(shí)現(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指針以及父類(lèi)的isa指針指向和cls做對(duì)比,通過(guò)上面isa的指向圖,我們對(duì)上面判斷一一解釋下:

BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];       // 1

因?yàn)镹SObject的isa指向NSObject的元類(lèi),先拿到NSObject的元類(lèi)跟NSObject比,不通過(guò),而NSObject元類(lèi)的isa指向的是NSObject,然后跟NSObject對(duì)比,所以結(jié)果是YES

BOOL re3 = [(id)[LGPerson class] isKindOfClass:[LGPerson class]];       // 0

而LGPerson的isa指向依次是LGPerson的元類(lèi) ---> NSObject的元類(lèi) ---> NSObject --- > nil,然后和LGPerson進(jìn)行對(duì)比,沒(méi)有匹配的,所以結(jié)果是NO

  • 3、再看一下類(lèi)的isMemberOfClass的實(shí)現(xiàn)
+ (BOOL)isMemberOfClass:(Class)cls {
    return object_getClass((id)self) == cls;
}

我們發(fā)現(xiàn)isMemberOfClass僅僅是拿到當(dāng)前self的isa指針指向和cls對(duì)比,然后我們分析測(cè)試代碼邏輯:

BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];     // 0

NSObject的元類(lèi)和NSObject不匹配,所以不成立

BOOL re4 = [(id)[LGPerson class] isMemberOfClass:[LGPerson class]];     // 0

LGPerson的元類(lèi)和LGPerson不匹配,所以不成立

  • 4、再看一下實(shí)例方法的isKindOfClass方法和isMemberOfClass的實(shí)現(xiàn)
- (BOOL)isKindOfClass:(Class)cls {
    // 類(lèi)  - NSObject 類(lèi) vs 父類(lèi) 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);
}

這里有一個(gè)iOS交流圈:891 488 181 分享BAT,阿里面試題、面試經(jīng)驗(yàn),討論技術(shù),有興趣的可以進(jìn)來(lái)了解。
我們可以發(fā)現(xiàn)對(duì)于對(duì)象方法,只是拿到對(duì)象的isa指向和相應(yīng)的類(lèi)對(duì)比,而對(duì)象的isa指向的都是相應(yīng)的類(lèi),所以下面四個(gè)輸出結(jié)果都是YES。

二、[super class]和[self class]

題目:

我們創(chuàng)建一個(gè)集成LGPerson的類(lèi)LGStudent的類(lèi),然后在LGStudent的實(shí)例方法里面寫(xiě)下面代碼,然后調(diào)用該對(duì)象方法:

-(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呢,不應(yīng)該是LGPerson嗎,下面我們來(lái)探索下:

答案:

1、匯編分析法

首先我們開(kāi)啟下匯編調(diào)試模式(),在[super class]處加個(gè)斷點(diǎn),看看在[super class]處是怎么調(diào)用的

我們發(fā)現(xiàn)[super class]是通過(guò)objc_msgSendSuper2進(jìn)行發(fā)送消息的,而不是通過(guò)objc_msgSend發(fā)送消息的,我們?cè)俚給bjc源碼中去找一下objc_msgSendSuper2的實(shí)現(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

我們最終在匯編地方找到了實(shí)現(xiàn),并且發(fā)現(xiàn)_objc_msgSendSuper2的參數(shù)分別為objc_super、SEL等等,其中objc_super是消息接受者,并且它是一個(gè)結(jié)構(gòu)體:

 * struct objc_super {
 *     id receiver;
 *     Class cls;   // SUBCLASS of the class to search
 * }

我們知道receiver是self,cls是self的父類(lèi),_objc_msgSendSuper2其實(shí)就從self的父類(lèi)開(kāi)始查找方法,但是消息接受者還是self本身,也就類(lèi)似是讓self去調(diào)父類(lèi)的class方法,所以返回的都是LGStudent

2、hook分析法:

我們創(chuàng)建一個(gè)NSObject的分類(lèi),然后在里面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(@"來(lái)了,老弟");
    return [self lg_class]; // sel -> imp(class)
}

我們?cè)趌g_class里面打一個(gè)斷點(diǎn),通過(guò)lldb來(lái)看一下調(diào)進(jìn)來(lái)的self是什么: 打印結(jié)果:

2020-01-16 11:04:30.876482+0800 LGTest[19658:395083] 來(lái)了,老弟
(lldb) p self
(LGStudent *) $0 = 0x00000001022327c0
2020-01-16 11:04:54.903791+0800 LGTest[19658:395083] 來(lái)了,老弟
(lldb) p self
(NSTaggedPointerString *) $1 = 0xd76f961d90151cc3 @"LGStudent"
2020-01-16 11:05:07.057101+0800 LGTest[19658:395083] LGStudent

我們發(fā)現(xiàn)調(diào)進(jìn)來(lái)的self都是LGStudent,所以也驗(yàn)證了[super class]的調(diào)用者還是self本身

三、weak和strong底層原理

問(wèn)題:

__weak我們?cè)陧?xiàng)目中經(jīng)常用于打破循環(huán)引用,但為什么weak可以打破循環(huán)引用呢?strong又是怎么回事呢?

答案:

weak

我們?cè)趏bjc源碼中的main方法中寫(xiě)上下面這句代碼,打上斷點(diǎn)并打開(kāi)匯編調(diào)試:

LGPerson __weak *objc = object;

然后我們發(fā)現(xiàn)在此處調(diào)用了objc_initWeak方法,我們?cè)冱c(diǎn)擊進(jìn)去:

/** 
 * 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的使用方法,并且介紹了該方法是用來(lái)初始化對(duì)象弱指針的,并且是線程不安全的,根據(jù)代碼進(jìn)入到了storeWeak函數(shù),我們?cè)龠M(jìn)入到storeWeak里面看看。

源碼太長(zhǎng)就先不放了,根據(jù)源碼分析,前部分都是對(duì)表進(jìn)行判斷的,并且我們知道弱引用指針是存在一個(gè)叫SideTable的表中,再往下我們發(fā)現(xiàn)如果沒(méi)表就走weak_register_no_lock函數(shù),看名字知道應(yīng)該是注冊(cè)弱引用指針的方法,如果有就走weak_unregister_no_lock方法

我們?cè)龠M(jìn)入到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;//判斷該對(duì)象是否在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;//判斷表里有沒(méi)有這個(gè)對(duì)象的子表,如果有就從weak_table中取出weak_entry_t然后將弱指針插入到weak_entry_t中
    if ((entry = weak_entry_for_referent(weak_table, referent))) {
        append_referrer(entry, referrer);
    } 
    else { //如果沒(méi)有就創(chuàng)建一個(gè)weak_entry_t,再將這個(gè)weak_entry_t插入到weak_table中去
        // 創(chuàng)建了這個(gè)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;
}

從上我們會(huì)發(fā)現(xiàn)weak指針在創(chuàng)建的時(shí)候并沒(méi)有調(diào)用retain操作,并且會(huì)將weak指針存儲(chǔ)在SideTable的weak_table中,然后每個(gè)對(duì)象在weak_table里面都有一個(gè)對(duì)應(yīng)的weak_entry_t,每個(gè)weak_entry_t里面可以放多個(gè)弱指針

strong

有了weak我們?cè)倏纯磗trong是什么情況呢? 我們依然打開(kāi)匯編調(diào)試,然后將__weak改成__strong然后運(yùn)行

 LGPerson __strong *objc = object;

我們發(fā)現(xiàn)此處調(diào)用的是objc_retain,command+點(diǎn)擊,進(jìn)不去,我們就在OBJC源碼里面搜,也搜不到,怎么辦呢,考慮到匯編一般會(huì)在函數(shù)前面添加,我們?nèi)サ鬫再次搜索,然后我們找到了objc_retain函數(shù)

id 
objc_retain(id obj)
{
    if (!obj) return obj;
    if (obj->isTaggedPointer()) return obj;
    return obj->retain();
}

然后進(jìn)入到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)是通過(guò)objc_msgSend發(fā)送了SEL_retain消息,然后讓引用計(jì)數(shù)器+1

四、runtime的應(yīng)用以及注意點(diǎn)

問(wèn)題:

我們經(jīng)常在項(xiàng)目中用的runtime,并且用的最多的是交換方法,在交換方法中有哪些注意事項(xiàng)呢:

答案:

1、NSArray 類(lèi)簇,

類(lèi)簇實(shí)際上是Foundation framework框架下的一種設(shè)計(jì)模式,它管理了一組隱藏在公共接口下的私有類(lèi),

所以涉及到類(lèi)簇的類(lèi),NSDictionary、NSArray、,本身類(lèi)并不是NSArray等,這個(gè)需要去確定該類(lèi)是否是類(lèi)簇,然后在確定真正的類(lèi)是什么,然后對(duì)真正的類(lèi)進(jìn)行交換才行

2、交換的方法是父類(lèi)的方法

如果交換的方法是父類(lèi)的方法,就會(huì)導(dǎo)致當(dāng)父類(lèi)調(diào)用該方法時(shí)候報(bào)錯(cuò),因?yàn)楦割?lèi)沒(méi)有子類(lèi)的方法。

解決方法就是:先嘗試給交換的類(lèi)添加要交換的方法,如果添加成功,說(shuō)明自己沒(méi)有這個(gè)方法,那么就對(duì)該類(lèi)做替換操作,如果添加失敗說(shuō)明自己有這個(gè)方法,那么就直接做交換操作。 代碼:

+ (void)lg_betterMethodSwizzlingWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL{

    if (!cls) NSLog(@"傳入的交換類(lèi)不能為空");

    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,因?yàn)樯厦鎜riMethod可能是cls父類(lèi)的,交換的話可能會(huì)導(dǎo)致父類(lèi)找不到swizzledSEL方法
    Method oriMethod1 = class_getInstanceMethod(cls, oriSEL);
    //交換方法
    method_exchangeImplementations(oriMethod1, swiMethod);
/**************************************************************/
   //方式二:

//嘗試添加,如果添加成功,說(shuō)明自己沒(méi)有這個(gè)方法,那么就對(duì)該類(lèi)做替換操作,因?yàn)榇颂幗ooriSEL方法添加的方法指針是swiMethod的方法指針,那么swizzledSEL的指針就要改成oriSEL的指針
    //如果添加失敗說(shuō)明自己有這個(gè)方法,那么就直接做交換操作
//    BOOL isSuccess = class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(oriMethod));
//
//    if (isSuccess) {// 自己沒(méi)有 - 交換 - 沒(méi)有父類(lèi)進(jìn)行處理 (重寫(xiě)一個(gè))
//        class_replaceMethod(cls, swizzledSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
//
//    }else{ // 自己有就做交換操作
//        method_exchangeImplementations(oriMethod, swiMethod);
//    }
}

本質(zhì)就是先給cls添加oriSEL方法,確保cls有了oriSEL方法后再交換,這樣就不會(huì)交換到cls父類(lèi)的方法

3、交換的方法不存在

假如交換的方法不存在,就會(huì)導(dǎo)致交換失敗,那么就要在上面代碼中單獨(dú)處理下單獨(dú)處理下:

    Method oriMethod = class_getInstanceMethod(cls, oriSEL);
    Method swiMethod = class_getInstanceMethod(cls, swizzledSEL);

    if (!oriMethod) {
        // 在oriMethod為nil時(shí),替換后將swizzledSEL復(fù)制一個(gè)不做任何事的空實(shí)現(xiàn),代碼如下:
        class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod));
        method_setImplementation(swiMethod, imp_implementationWithBlock(^(id self, SEL _cmd){ }));
    }

當(dāng)方法不存在時(shí)候就需要單獨(dú)給cls添加一個(gè)方法實(shí)現(xiàn)

五、內(nèi)存偏移面試題

問(wèn)題:

我們創(chuàng)建一個(gè)有saySomething實(shí)例方法的LGPerson的類(lèi),問(wèn)下面代碼能執(zhí)行嗎?

    id pcls = [LGPerson class];
    void *pp= &pcls;
    [(__bridge id)pp saySomething];

    //p -> LGPerson 實(shí)例對(duì)象
    LGPerson *p = [LGPerson alloc];
    [p saySomething];

答案:

答案是可以執(zhí)行的。為什么呢?我們來(lái)分析下:

  • 1、首先對(duì)象的本質(zhì)是個(gè)結(jié)構(gòu)體,并且第一個(gè)元素是isa,isa指向的是對(duì)象的類(lèi)對(duì)象
  • 2、我們知道指針指向的對(duì)象的地址其實(shí)就是對(duì)象的首地址,所以p指向的是LGPerson對(duì)象的首地址isa
  • 3、而isa指向的是LGPerson的類(lèi)對(duì)象,那么對(duì)象指針就形成了這樣的指向關(guān)系:p ---> 對(duì)象 ---> LGPerson類(lèi)對(duì)象
  • 4、再看上面的pcls:pcls指向的是LGPerson的類(lèi)對(duì)象,pp又指向的是pcls,這樣也形成了一個(gè)指向關(guān)系:pp ---> pcls ---> LGPerson類(lèi)對(duì)象
  • 5、兩者對(duì)比起來(lái),p和pp性質(zhì)就是一樣的了,所以上面兩個(gè)都能執(zhí)行

疑問(wèn):但是對(duì)象是個(gè)結(jié)構(gòu)體可以執(zhí)行方法,&pcls只是個(gè)地址為啥也能執(zhí)行方法

我們?cè)贁U(kuò)展一下: 我們將saySomething重寫(xiě)一下,并且給LGPerson增加一個(gè)NSString屬性name

- (void)saySomething{
    NSLog(@"NB %s - %@",__func__,self.name);
}

在執(zhí)行一下,看一下打?。?/p>

2020-01-16 21:07:16.278767+0800 LGTest[50715:802279] NB -[LGPerson saySomething] - <ViewController: 0x7f84047063b0>

這又是為啥呢,我們猜測(cè)一下這個(gè)地方是野指針,正好ViewController對(duì)象在那一塊,但是我們多次運(yùn)行測(cè)試后結(jié)果卻一樣,我們來(lái)分析一下:

我們做一個(gè)測(cè)試,在方法中寫(xiě)下如下代碼:

    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);

得到打印結(jié)果

a = 0x7ffee837d19c
b = 0x7ffee837d198
c = 0x7ffee837d194
d = 0x7ffee837d190

我們發(fā)現(xiàn)a、b、c、d地址是連續(xù)的,且abcd都是臨時(shí)變量,變量是以棧的形式存儲(chǔ),

  • 1、我們知道OC對(duì)象本質(zhì)是結(jié)構(gòu)體,里面第一個(gè)元素是isa,然后下面元素依次是對(duì)象的屬性
  • 2、當(dāng)我們只有一個(gè)屬性時(shí)候,對(duì)象訪問(wèn)屬性其實(shí)就是將對(duì)象的指針下移屬性大小的位置
  • 3、那么上面那只方式其實(shí)訪問(wèn)的就是pp下移8個(gè)字節(jié)的位置的數(shù)據(jù)
  • 4、每個(gè)方法都有兩個(gè)隱藏參數(shù)super和self,所以在方法中臨時(shí)變量順序是super、self、pcls、pp,pp指向的是pcls
  • 5、當(dāng)我們用pp獲取name的時(shí)候,本質(zhì)上就是得到pcls指針然后往下移name的大小的位置,然后讀取值,name大小是8字節(jié),pcls大小也是8字節(jié),所以移完之后正好指向了self

這就是為啥打印出<ViewController: 0x7f84047063b0>的原因。

我們可以再做一個(gè)實(shí)驗(yàn),在代碼前加上一個(gè)NSString臨時(shí)變量

NSString *tem = @"KC";
id pcls = [LGPerson class];
void *pp= &pcls;
[(__bridge id)pp saySomething];

再執(zhí)行,打?。?/p>

2020-01-16 21:43:05.084478+0800 LGTest[52497:844500] NB -[LGPerson saySomething] - KC

更印證了我們的結(jié)果

五、關(guān)聯(lián)對(duì)象的原理

問(wèn)題:

分類(lèi)中如何創(chuàng)建屬性?

答案:

在分類(lèi)中創(chuàng)建屬性,我們一般會(huì)寫(xiě)上一個(gè)屬性,然后實(shí)現(xiàn)該屬性的set和get方法,再關(guān)聯(lián)對(duì)象,這是為什么呢,我們來(lái)一步步分析:

1、創(chuàng)建屬性

在分類(lèi)中創(chuàng)建了一個(gè)屬性,會(huì)在rw中屬性列表中有數(shù)據(jù),然后有了set和get方法,但是該屬性沒(méi)有成員變量,需要重寫(xiě)該屬性的set/get方法來(lái)保存屬性值。

2、重寫(xiě)set/get方法,關(guān)聯(lián)對(duì)象

首先我們來(lái)看下set方法

-(void)setCate_name:(NSString *)cate_name{
    /**
    參數(shù)一:id object : 給哪個(gè)對(duì)象添加屬性,這里要給自己添加屬性,用self。
    參數(shù)二:void * == id key : 屬性名,根據(jù)key獲取關(guān)聯(lián)對(duì)象的屬性的值,在objc_getAssociatedObject中通過(guò)次key獲得屬性的值并返回。
    參數(shù)三:id value : 關(guān)聯(lián)的值,也就是set方法傳入的值給屬性去保存。
    參數(shù)四:objc_AssociationPolicy policy : 策略,屬性以什么形式保存。
    */
    objc_setAssociatedObject(self, @"name",cate_name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }

然后我們?cè)倏纯磑bjc_setAssociatedObject方法:

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

這里我們會(huì)發(fā)現(xiàn),蘋(píng)果對(duì)外接口,一般都有一個(gè)加_的對(duì)內(nèi)接口與之對(duì)應(yīng),這是蘋(píng)果為了解耦合,即使底層內(nèi)部實(shí)現(xiàn)了也不會(huì)影響到對(duì)外接口,我們?cè)倏匆幌耞object_set_associative_reference的實(shí)現(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會(huì)對(duì)retain和copy進(jìn)行操作,
    id new_value = value ? acquireValue(value, policy) : nil;
    {
        // 關(guān)聯(lián)對(duì)象的管理類(lèi)
        AssociationsManager manager;
        // 獲取關(guān)聯(lián)的 HashMap -> 存儲(chǔ)當(dāng)前關(guān)聯(lián)對(duì)象
        AssociationsHashMap &associations(manager.associations());
        // 對(duì)當(dāng)前的對(duì)象的地址做按位去反操作 - 就是 HashMap 的key (哈希函數(shù))
        disguised_ptr_t disguised_object = DISGUISE(object);
        if (new_value) {
            // break any existing association.
            // 獲取 AssociationsHashMap 的迭代器 - (對(duì)象的) 進(jìn)行遍歷
            AssociationsHashMap::iterator i = associations.find(disguised_object);
            if (i != associations.end()) {
                // secondary table exists
                ObjectAssociationMap *refs = i->second;
                // 根據(jù)key去獲取關(guān)聯(lián)屬性的迭代器
                ObjectAssociationMap::iterator j = refs->find(key);
                if (j != refs->end()) {
                    old_association = j->second;
                    // 替換設(shè)置新值
                    j->second = ObjcAssociation(policy, new_value);
                } else {
                    // 到最后了 - 直接設(shè)置新值
                    (*refs)[key] = ObjcAssociation(policy, new_value);
                }
            } else {
                // create the new association (first time).
                // 如果AssociationsHashMap從沒(méi)有對(duì)象的關(guān)聯(lián)信息表,
                // 那么就創(chuàng)建一個(gè)map并通過(guò)傳入的key把value存進(jìn)去
                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存儲(chǔ)過(guò)關(guān)聯(lián)對(duì)象,
            // 那么就把這個(gè)關(guān)聯(lián)的value移除(這也是為什么傳入nil對(duì)象能夠把對(duì)象的關(guān)聯(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).
    // 最后把之前使用傳入的這個(gè)key存儲(chǔ)的關(guān)聯(lián)的value釋放(OBJC_ASSOCIATION_SETTER_RETAIN策略存儲(chǔ)的)
    if (old_association.hasValue()) ReleaseValue()(old_association);
}

從上面我們梳屢一下邏輯:

  • 1、先對(duì)傳進(jìn)來(lái)的值做下retain或者copy處理得到new_value
  • 2、再獲取到管理所有關(guān)聯(lián)對(duì)象的哈希map總表的管理者AssociationsManager,然后拿到哈希map總表AssociationsHashMap
  • 3、對(duì)關(guān)聯(lián)對(duì)象的地址進(jìn)行取反操作得到哈希表對(duì)應(yīng)的下標(biāo)index(其實(shí)disguised_ptr_t就是一個(gè)long類(lèi)型的)
  • 4、如果得到的new_value不為空的話,就拿到總表的迭代器通過(guò)拿到的下標(biāo)index進(jìn)行遍歷查找
  • 5、如果找到管理對(duì)象的關(guān)聯(lián)屬性哈希map表,然后再通過(guò)key去遍歷取值,
    • 如果取到了,就先把新值設(shè)置到key上,再將舊值釋放掉
    • 如果沒(méi)取到,就直接將新值設(shè)置在key上
  • 6、如果沒(méi)找到關(guān)聯(lián)對(duì)象的關(guān)聯(lián)屬性哈希map表,就創(chuàng)建一個(gè)表,然后將新值設(shè)置在key上
  • 7、如果得到的new_value為空的話,就嘗試取值,取到了的話就將key對(duì)應(yīng)的值置為nil,如果取不到就不做處理

我們?cè)倏匆幌耾bjc_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;
    {
        // 關(guān)聯(lián)對(duì)象的管理類(lèi)
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.associations());
        // 生成偽裝地址。處理參數(shù) object 地址
        disguised_ptr_t disguised_object = DISGUISE(object);
        // 所有對(duì)象的額迭代器
        AssociationsHashMap::iterator i = associations.find(disguised_object);
        if (i != associations.end()) {
            ObjectAssociationMap *refs = i->second;
            // 內(nèi)部對(duì)象的迭代器
            ObjectAssociationMap::iterator j = refs->find(key);
            if (j != refs->end()) {
                // 找到 - 把值和策略讀取出來(lái)
                ObjcAssociation &entry = j->second;
                value = entry.value();
                policy = entry.policy();
                // OBJC_ASSOCIATION_GETTER_RETAIN - 就會(huì)持有一下
                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反過(guò)來(lái)而已~

作者:海浪寶寶
鏈接:https://juejin.cn/post/6844904049481875470

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容