八 OC底層原理 方法查找

簡介

在前面我們知道僻澎,當(dāng)我們使用xcrun 將文件編譯成cpp文件的時候 就可以看到方法的本質(zhì)就是消息废封,調(diào)用方法也就是發(fā)送消息州泊,這就有一個很重要的函數(shù) objc_msgSend, 下面我們就來看看 消息發(fā)送的實現(xiàn)

從下面的注釋,我們可以知道漂洋,objc_msgSend 就是發(fā)送一個消息給類的實例對象
可以從note 處看到 那么多方法都是基于 objc_msgSend實現(xiàn)的

/** 
 * Sends a message with a simple return value to an instance of a class.
 * 
 * @param self A pointer to the instance of the class that is to receive the message.
 * @param op The selector of the method that handles the message.
 * @param ... 
 *   A variable argument list containing the arguments to the method.
 * 
 * @return The return value of the method.
 * 
 * @note When it encounters a method call, the compiler generates a call to one of the
 *  functions \c objc_msgSend, \c objc_msgSend_stret, \c objc_msgSendSuper, or \c objc_msgSendSuper_stret.
 *  Messages sent to an object’s superclass (using the \c super keyword) are sent using \c objc_msgSendSuper; 
 *  other messages are sent using \c objc_msgSend. Methods that have data structures as return values
 *  are sent using \c objc_msgSendSuper_stret and \c objc_msgSend_stret.
 */
OBJC_EXPORT id _Nullable
objc_msgSend(id _Nullable self, SEL _Nonnull op, ...)
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);

一 方法的快速查找

1. objc_msgSend 的具體實現(xiàn)

我們在前面的編譯的源碼中遥皂,全局搜索 objc_msgSend ,在objc-msg-arm64.s 文件中 我們可以看到這樣一段代碼
objc_msgSend 函數(shù)的是用匯編實現(xiàn)的,

    ENTRY _objc_msgSend // ENTRY 表示函數(shù)的入口刽漂,是一個格式
    UNWIND _objc_msgSend, NoFrame

    cmp p0, #0          // nil check and tagged pointer check
#if SUPPORT_TAGGED_POINTERS  //在arm64-asm.h 可以看到 當(dāng)arm64架構(gòu)時是true
    b.le    LNilOrTagged        //  (MSB tagged pointer looks negative)
#else
    b.eq    LReturnZero
#endif
     // 根據(jù)isa演训, 獲取類對象
    ldr p13, [x0]       // p13 = isa
    GetClassFromIsa_p16 p13     // p16 = class
LGetIsaDone:
    // calls imp or objc_msgSend_uncached -- 調(diào)用imp方法實現(xiàn),或者調(diào)用objc_msgSend_uncached
    CacheLookup NORMAL, _objc_msgSend
#if SUPPORT_TAGGED_POINTERS
LNilOrTagged:
    b.eq    LReturnZero     // nil check

    // tagged
    adrp    x10, _objc_debug_taggedpointer_classes@PAGE
    add x10, x10, _objc_debug_taggedpointer_classes@PAGEOFF
    ubfx    x11, x0, #60, #4
    ldr x16, [x10, x11, LSL #3]
    adrp    x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGE
    add x10, x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGEOFF
    cmp x10, x16
    b.ne    LGetIsaDone

    // ext tagged
    adrp    x10, _objc_debug_taggedpointer_ext_classes@PAGE
    add x10, x10, _objc_debug_taggedpointer_ext_classes@PAGEOFF
    ubfx    x11, x0, #52, #8
    ldr x16, [x10, x11, LSL #3]
    b   LGetIsaDone
// SUPPORT_TAGGED_POINTERS
#endif

LReturnZero:
    // x0 is already zero
    mov x1, #0
    movi    d0, #0
    movi    d1, #0
    movi    d2, #0
    movi    d3, #0
    ret

    END_ENTRY _objc_msgSend

2.CacheLookup 實現(xiàn)

我們詳細看一下 CacheLookup 怎么定義的
從下面的注釋中我們可以看到贝咙,這個方法的含義是 從類的緩存中獲取方法的imp

.macro CacheLookup
    //
    // Restart protocol:
    //
    //   As soon as we're past the LLookupStart$1 label we may have loaded
    //   an invalid cache pointer or mask.
    //
    //   When task_restartable_ranges_synchronize() is called,
    //   (or when a signal hits us) before we're past LLookupEnd$1,
    //   then our PC will be reset to LLookupRecover$1 which forcefully
    //   jumps to the cache-miss codepath which have the following
    //   requirements:
    //
    //   GETIMP:
    //     The cache-miss is just returning NULL (setting x0 to 0)
    //
    //   NORMAL and LOOKUP:
    //   - x0 contains the receiver
    //   - x1 contains the selector
    //   - x16 contains the isa
    //   - other registers are set as per calling conventions
    //
LLookupStart$1:
    // 注釋
    //  通過isa 偏移拿到類的方法緩存中的buckets样悟、以及occupied和mask
    // p1 = SEL, p16 = isa
    ldr p11, [x16, #CACHE]              // p11 = mask|buckets

#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
    and p10, p11, #0x0000ffffffffffff   // p10 = buckets
    and p12, p1, p11, LSR #48       // x12 = _cmd & mask
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
    and p10, p11, #~0xf         // p10 = buckets
    and p11, p11, #0xf          // p11 = maskShift
    mov p12, #0xffff
    lsr p11, p12, p11               // p11 = mask = 0xffff >> p11
    and p12, p1, p11                // x12 = _cmd & mask
#else
#error Unsupported cache mask storage for ARM64.
#endif


    add p12, p10, p12, LSL #(1+PTRSHIFT)
                     // p12 = buckets + ((_cmd & mask) << (1+PTRSHIFT))
// 在 bucket 中查找方法,也就是在類的方法緩存中查找方法庭猩,找到就返回
// 也就是 CacheHit 在上面窟她,可以看到方法實現(xiàn) ,也就是方法簽名蔼水,并且調(diào)用方法
    ldp p17, p9, [x12]      // {imp, sel} = *bucket
1:  cmp p9, p1          // if (bucket->sel != _cmd)
    b.ne    2f          //     scan more
    CacheHit $0         // call or return imp
// 方法沒有在緩存中找到震糖,此時調(diào)用 CheckMiss 函數(shù),    在CheckMiss函數(shù)實現(xiàn)中徙缴,又調(diào)用了 __objc_msgSend_uncached
2:  // not hit: p12 = not-hit bucket
    CheckMiss $0            // miss if bucket->sel == 0
    cmp p12, p10        // wrap if bucket == buckets
    b.eq    3f
    ldp p17, p9, [x12, #-BUCKET_SIZE]!  // {imp, sel} = *--bucket
    b   1b          // loop

3:  // wrap: p12 = first bucket, w11 = mask
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
    add p12, p12, p11, LSR #(48 - (1+PTRSHIFT))
                    // p12 = buckets + (mask << 1+PTRSHIFT)
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
    add p12, p12, p11, LSL #(1+PTRSHIFT)
                    // p12 = buckets + (mask << 1+PTRSHIFT)
#else
#error Unsupported cache mask storage for ARM64.
#endif

    // Clone scanning loop to miss instead of hang when cache is corrupt.
    // The slow path may detect any corruption and halt later.

    ldp p17, p9, [x12]      // {imp, sel} = *bucket
1:  cmp p9, p1          // if (bucket->sel != _cmd)
    b.ne    2f          //     scan more
    CacheHit $0         // call or return imp
    
2:  // not hit: p12 = not-hit bucket
    CheckMiss $0            // miss if bucket->sel == 0
    cmp p12, p10        // wrap if bucket == buckets
    b.eq    3f
    ldp p17, p9, [x12, #-BUCKET_SIZE]!  // {imp, sel} = *--bucket
    b   1b          // loop

LLookupEnd$1:
LLookupRecover$1:
3:  // double wrap
    JumpMiss $0

.endmacro

這個方法的主體就是查找 cache_t试伙,并且查找cache_t中是否有傳進來的方法嘁信。 如果緩存中存在就執(zhí)行 CacheHit,不存在就執(zhí)行 CheckMiss

3. CacheHit

  1. 由于之前是normal 查找于样,所以就直接簽名并返回了imp疏叨,
/********************************************************************
 *
 * CacheLookup NORMAL|GETIMP|LOOKUP <function>
 *
 * Locate the implementation for a selector in a class method cache.
 *
 * When this is used in a function that doesn't hold the runtime lock,
 * this represents the critical section that may access dead memory.
 * If the kernel causes one of these functions to go down the recovery
 * path, we pretend the lookup failed by jumping the JumpMiss branch.
 *
 * Takes:
 *   x1 = selector
 *   x16 = class to be searched
 *
 * Kills:
 *   x9,x10,x11,x12, x17
 *
 * On exit: (found) calls or returns IMP
 *                  with x16 = class, x17 = IMP
 *          (not found) jumps to LCacheMiss
 *
 ********************************************************************/

#define NORMAL 0
#define GETIMP 1
#define LOOKUP 2

// CacheHit: x17 = cached IMP, x12 = address of cached IMP, x1 = SEL, x16 = isa
.macro CacheHit
.if $0 == NORMAL
    TailCallCachedImp x17, x12, x1, x16 // authenticate and call imp
.elseif $0 == GETIMP
    mov p0, p17
    cbz p0, 9f          // don't ptrauth a nil imp
    AuthAndResignAsIMP x0, x12, x1, x16 // authenticate imp and re-sign as IMP
9:  ret             // return IMP
.elseif $0 == LOOKUP
    // No nil check for ptrauth: the caller would crash anyway when they
    // jump to a nil IMP. We don't care if that jump also fails ptrauth.
    AuthAndResignAsIMP x17, x12, x1, x16    // authenticate imp and re-sign as IMP
    ret             // return imp via x17
.else
.abort oops
.endif
.endmacro

4. CheckMiss

可知,如果方法沒有在緩存中找到穿剖,就執(zhí)行了CheckMiss蚤蔓,之前的類型是 NORMAl,也就執(zhí)行了 __objc_msgSend_uncached

.macro CheckMiss
    // miss if bucket->sel == 0
.if $0 == GETIMP
    cbz p9, LGetImpMiss
.elseif $0 == NORMAL
    cbz p9, __objc_msgSend_uncached
.elseif $0 == LOOKUP
    cbz p9, __objc_msgLookup_uncached
.else
.abort oops
.endif
.endmacro

二 方法的慢速查找

1. __objc_msgSend_uncached

全局搜索 糊余,可以看到這樣一段代碼秀又, 可以看到函數(shù)內(nèi)部 中有有一段說明 這不是一個C函數(shù)方法,可以在類中搜索贬芥,也就是MethodTableLookup

    STATIC_ENTRY __objc_msgSend_uncached
    UNWIND __objc_msgSend_uncached, FrameWithNoSaves

    // THIS IS NOT A CALLABLE C FUNCTION
    // Out-of-band p16 is the class to search
    
    MethodTableLookup
    TailCallFunctionPointer x17

    END_ENTRY __objc_msgSend_uncached

2. MethodTableLookup

可以看到 內(nèi)部調(diào)用了 lookUpImpOrForward 函數(shù)吐辙, 參數(shù)是 obj,sel蘸劈,class 等

.macro MethodTableLookup
    
    // push frame
    SignLR
    stp fp, lr, [sp, #-16]!
    mov fp, sp

    // save parameter registers: x0..x8, q0..q7
    sub sp, sp, #(10*8 + 8*16)
    stp q0, q1, [sp, #(0*16)]
    stp q2, q3, [sp, #(2*16)]
    stp q4, q5, [sp, #(4*16)]
    stp q6, q7, [sp, #(6*16)]
    stp x0, x1, [sp, #(8*16+0*8)]
    stp x2, x3, [sp, #(8*16+2*8)]
    stp x4, x5, [sp, #(8*16+4*8)]
    stp x6, x7, [sp, #(8*16+6*8)]
    str x8,     [sp, #(8*16+8*8)]

    // lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER)
    // receiver and selector already in x0 and x1
    mov x2, x16
    mov x3, #3
    bl  _lookUpImpOrForward

    // IMP in x0
    mov x17, x0
    
    // restore registers and return
    ldp q0, q1, [sp, #(0*16)]
    ldp q2, q3, [sp, #(2*16)]
    ldp q4, q5, [sp, #(4*16)]
    ldp q6, q7, [sp, #(6*16)]
    ldp x0, x1, [sp, #(8*16+0*8)]
    ldp x2, x3, [sp, #(8*16+2*8)]
    ldp x4, x5, [sp, #(8*16+4*8)]
    ldp x6, x7, [sp, #(8*16+6*8)]
    ldr x8,     [sp, #(8*16+8*8)]

    mov sp, fp
    ldp fp, lr, [sp], #16
    AuthenticateLR

.endmacro

3. lookUpImpOrForward

最終我們在 objc-runtime-new.mm 文件中找到函數(shù)實現(xiàn)


/***********************************************************************
* lookUpImpOrForward.
* The standard IMP lookup. 
* Without LOOKUP_INITIALIZE: tries to avoid +initialize (but sometimes fails)
* Without LOOKUP_CACHE: skips optimistic unlocked lookup (but uses cache elsewhere)
* Most callers should use LOOKUP_INITIALIZE and LOOKUP_CACHE
* inst is an instance of cls or a subclass thereof, or nil if none is known. 
*   If cls is an un-initialized metaclass then a non-nil inst is faster.
* May return _objc_msgForward_impcache. IMPs destined for external use 
*   must be converted to _objc_msgForward or _objc_msgForward_stret.
*   If you don't want forwarding at all, use LOOKUP_NIL.
**********************************************************************/
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
    const IMP forward_imp = (IMP)_objc_msgForward_impcache;
    IMP imp = nil;
    Class curClass;
// 防止線程并發(fā)操作 加鎖
    runtimeLock.assertUnlocked();
// 判斷緩存中是否存在昏苏,存在就返回imp
    // Optimistic cache lookup
    if (fastpath(behavior & LOOKUP_CACHE)) {
        imp = cache_getImp(cls, sel);
        if (imp) goto done_nolock;
    }

    // runtimeLock is held during isRealized and isInitialized checking
    // to prevent races against concurrent realization.

    // runtimeLock is held during method search to make
    // method-lookup + cache-fill atomic with respect to method addition.
    // Otherwise, a category could be added but ignored indefinitely because
    // the cache was re-filled with the old value after the cache flush on
    // behalf of the category.

    runtimeLock.lock();

    // We don't want people to be able to craft a binary blob that looks like
    // a class but really isn't one and do a CFI attack.
    //
    // To make these harder we want to make sure this is a class that was
    // either built into the binary or legitimately registered through
    // objc_duplicateClass, objc_initializeClassPair or objc_allocateClassPair.
    //
    // TODO: this check is quite costly during process startup.
   //判斷該類是否是已知類
    checkIsKnownClass(cls);
    // //判斷當(dāng)前類是否加載到內(nèi)存中(isa和rw信息是否存在)。
    if (slowpath(!cls->isRealized())) {
        //如果不存在進行類的加載威沫,并且會遞歸加載所有父類贤惯、元類。為了確定類的父類棒掠、isa鏈
        cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
        // runtimeLock may have been dropped but is now locked again
    }
    //判斷類是否初始化孵构,同樣是遞歸判斷所有
    if (slowpath((behavior & LOOKUP_INITIALIZE) && !cls->isInitialized())) {
        cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
        // runtimeLock may have been dropped but is now locked again

        // If sel == initialize, class_initialize will send +initialize and 
        // then the messenger will send +initialize again after this 
        // procedure finishes. Of course, if this is not being called 
        // from the messenger then it won't happen. 2778172
    }

    runtimeLock.assertLocked();
    curClass = cls;

    // The code used to lookpu the class's cache again right after
    // we take the lock but for the vast majority of the cases
    // evidence shows this is a miss most of the time, hence a time loss.
    //
    // The only codepath calling into this without having performed some
    // kind of cache lookup is class_getInstanceMethod().
// 遍歷查詢
    for (unsigned attempts = unreasonableClassCount();;) {
        // curClass method list.
//使用二分法在當(dāng)前類的方法列表methodList中查找該方法
        Method meth = getMethodNoSuper_nolock(curClass, sel);
        if (meth) {
            imp = meth->imp;
            goto done;
        }
        //將當(dāng)前類切換為父類, 如果當(dāng)前類的父類是nil烟很,也就是在NSObject中都沒有找到則直接跳出循環(huán)
        if (slowpath((curClass = curClass->superclass) == nil)) {
            // No implementation found, and method resolver didn't help.
            // Use forwarding.
            imp = forward_imp;
            break;
        }

        // Halt if there is a cycle in the superclass chain.
        // 父類鏈中存在循環(huán)颈墅,停止
        if (slowpath(--attempts == 0)) {
            _objc_fatal("Memory corruption in class list.");
        }

        // Superclass cache.
//在父類的方法緩存中進行查找
        imp = cache_getImp(curClass, sel);
//如果父類沒找到,首次會進入動態(tài)方法解析
        if (slowpath(imp == forward_imp)) {
            // Found a forward:: entry in a superclass.
            // Stop searching, but don't cache yet; call method
            // resolver for this class first.
            break;
        }
// 如果方法找到了 就將其加入緩存
        if (fastpath(imp)) {
            // Found the method in a superclass. Cache it in this class.
            goto done;
        }
    }

    // No implementation found. Try method resolver once.
    // 沒有找到方法實現(xiàn),調(diào)用一次方法動態(tài)解析
    if (slowpath(behavior & LOOKUP_RESOLVER)) {
        behavior ^= LOOKUP_RESOLVER;
        return resolveMethod_locked(inst, sel, cls, behavior);
    }

 done:
    log_and_fill_cache(cls, imp, sel, inst, curClass);
    runtimeLock.unlock();
 done_nolock:
    if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
        return nil;
    }
    return imp;
}

4. 方法最后一次補救機會

我們可以看到雾袱,當(dāng)系統(tǒng)沒有找到imp 實現(xiàn)的時候恤筛, 就會跳出for循環(huán),進入這里的動態(tài)嘗試谜酒,這是慢速查找的補救措施叹俏,

if (slowpath(behavior & LOOKUP_RESOLVER)) {
        behavior ^= LOOKUP_RESOLVER;
        return resolveMethod_locked(inst, sel, cls, behavior);
    }

這段代碼只執(zhí)行一次,
第一進入時: behavior=3, LOOKUP_RESOLVER = 2,執(zhí)行與計算 behavior & LOOKUP_RESOLVER = 2僻族,此時 if 成立粘驰,
然后 behavior 等于 LOOKUP_RESOLVER的反,
也就是 下一次計算時是 一個數(shù)的反 與上這個數(shù)述么,結(jié)果一定為0蝌数,所以這個這個方法只會走一次
如果動態(tài)解析查找失, 就會 拋出forward_imp異常 度秘,就crash

補充 --- 動態(tài)方法決議 解析

resolveMethod_locked

接下看我們就看看 resolveMethod_locked 函數(shù)的執(zhí)行過程

static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
    runtimeLock.assertLocked();
    ASSERT(cls->isRealized());

    runtimeLock.unlock();
    // 動態(tài)方法決議: 重新開始查詢
    if (! cls->isMetaClass()) { // 對象查詢顶伞,類查詢
        // try [cls resolveInstanceMethod:sel]
        resolveInstanceMethod(inst, sel, cls);
    } 
    else { // 元類查詢
        // try [nonMetaClass resolveClassMethod:sel]
        // and [cls resolveInstanceMethod:sel]
        resolveClassMethod(inst, sel, cls);
        if (!lookUpImpOrNil(inst, sel, cls)) {
            resolveInstanceMethod(inst, sel, cls);
        }
    }

    // chances are that calling the resolver have populated the cache
    // so attempt using it
    // 再次調(diào)用
    return lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE);
}
  1. 函數(shù)入?yún)?/li>

inst: 表示真正持有sel 方法的類(對象方法存儲在類中饵撑,類方法 存放在 元類中)
sel: 方法名
cls: 方法接收的類,也就是調(diào)用方法的類
behavior: 影響方法的調(diào)用

  1. 方法解析
  1. 加鎖 -> 判定 類是否加載 -> 解鎖
  2. 判定類是否是元類唆貌,如果不是元類滑潘。就代表方法是對象方法,所以進入對象方法查詢
  3. 如果是類方法锨咙,就調(diào)用 resolveClassMethod 查詢類方法语卤,然后lookUpImpOrNil 檢查是否找到 imp, 如果沒有找到就調(diào)用 resolveInstanceMethod
    意思就是酪刀,如果類方法沿著元類繼承鏈中查找粹舵,直到找到 NSObject 元類中都沒有找到,接著就是 NSObject本類中骂倘,其中存儲的是對象方法眼滤,所以需要使用resolveInstanceMethod 在查找一次

resolveInstanceMethod

static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    // 獲取 resolveInstanceMethod 的方法名 SEL
    SEL resolve_sel = @selector(resolveInstanceMethod:);
    // 檢查 類對象的元類中是否有 resolveInstanceMethod 方法,如果沒有就 return; 但是在根元類中默認實現(xiàn)了該方法, 所以不會 return
    if (!lookUpImpOrNil(cls, resolve_sel, cls->ISA())) {
        // Resolver not implemented.
        return;
    }
    // 消息發(fā)送  調(diào)用一次 resolveInstanceMethod 方法
    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(cls, resolve_sel, sel);

    // 再次查詢一次 imp, 意思就是  如果上面的 resolveInstanceMethod 方法 實現(xiàn)了 sel, 我們就能拿到 imp,并將其寫入 cls 的緩存
    IMP imp = lookUpImpOrNil(inst, sel, cls);
    // 日志收集
    if (resolved  &&  PrintResolving) {
        if (imp) {
            _objc_inform("RESOLVE: method %c[%s %s] "
                         "dynamically resolved to %p", 
                         cls->isMetaClass() ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(sel), imp);
        }
        else {
            // Method resolver didn't add anything?
            _objc_inform("RESOLVE: +[%s resolveInstanceMethod:%s] returned YES"
                         ", but no new implementation of %c[%s %s] was found",
                         cls->nameForLogging(), sel_getName(sel), 
                         cls->isMetaClass() ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(sel));
        }
    }
}

lookUpImpOrNil

/* method lookup */
enum {
    LOOKUP_INITIALIZE = 1,
    LOOKUP_RESOLVER = 2,
    LOOKUP_CACHE = 4,
    LOOKUP_NIL = 8,
};
static inline IMP
lookUpImpOrNil(id obj, SEL sel, Class cls, int behavior = 0)
{
   // behavior = 0历涝, LOOKUP_CACHE = 4诅需, LOOKUP_NIL = 8
   return lookUpImpOrForward(obj, sel, cls, behavior | LOOKUP_CACHE | LOOKUP_NIL);
}

內(nèi)部繼續(xù)調(diào)用了 lookUpImpOrForward ,然后 behavior = 0|4|8 = 12

此時進入 lookUpImpOrForward 函數(shù)中, 以下條件就會成立睬关, 就會在緩存中查找一次
fastpath(behavior & LOOKUP_CACHE) = 12 & 4 = 4


 if (fastpath(behavior & LOOKUP_CACHE)) {
        imp = cache_getImp(cls, sel);
        if (imp) goto done_nolock;
    }

接著 (slowpath(behavior & LOOKUP_RESOLVER)) = 12 & 2 = 0 條件不成立诱担,就不會進入動態(tài)決議方法

  if (slowpath(behavior & LOOKUP_RESOLVER)) {
        behavior ^= LOOKUP_RESOLVER;
        return resolveMethod_locked(inst, sel, cls, behavior);
    }

所以 lookUpImpOrNil中的 lookUpImpOrForward會循環(huán)遍歷cls繼承鏈的所有類的cachemethodList來尋找imp

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市电爹,隨后出現(xiàn)的幾起案子蔫仙,更是在濱河造成了極大的恐慌,老刑警劉巖丐箩,帶你破解...
    沈念sama閱讀 216,692評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件摇邦,死亡現(xiàn)場離奇詭異,居然都是意外死亡屎勘,警方通過查閱死者的電腦和手機施籍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,482評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來概漱,“玉大人丑慎,你說我怎么就攤上這事∪看荩” “怎么了竿裂?”我有些...
    開封第一講書人閱讀 162,995評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長照弥。 經(jīng)常有香客問我腻异,道長,這世上最難降的妖魔是什么这揣? 我笑而不...
    開封第一講書人閱讀 58,223評論 1 292
  • 正文 為了忘掉前任悔常,我火速辦了婚禮影斑,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘机打。我一直安慰自己矫户,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,245評論 6 388
  • 文/花漫 我一把揭開白布姐帚。 她就那樣靜靜地躺著吏垮,像睡著了一般障涯。 火紅的嫁衣襯著肌膚如雪罐旗。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,208評論 1 299
  • 那天唯蝶,我揣著相機與錄音九秀,去河邊找鬼。 笑死粘我,一個胖子當(dāng)著我的面吹牛鼓蜒,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播征字,決...
    沈念sama閱讀 40,091評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼都弹,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了匙姜?” 一聲冷哼從身側(cè)響起畅厢,我...
    開封第一講書人閱讀 38,929評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎氮昧,沒想到半個月后框杜,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,346評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡袖肥,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,570評論 2 333
  • 正文 我和宋清朗相戀三年咪辱,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片椎组。...
    茶點故事閱讀 39,739評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡油狂,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出寸癌,到底是詐尸還是另有隱情专筷,我是刑警寧澤,帶...
    沈念sama閱讀 35,437評論 5 344
  • 正文 年R本政府宣布灵份,位于F島的核電站仁堪,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏填渠。R本人自食惡果不足惜弦聂,卻給世界環(huán)境...
    茶點故事閱讀 41,037評論 3 326
  • 文/蒙蒙 一鸟辅、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧莺葫,春花似錦匪凉、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,677評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至堡纬,卻和暖如春聂受,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背烤镐。 一陣腳步聲響...
    開封第一講書人閱讀 32,833評論 1 269
  • 我被黑心中介騙來泰國打工蛋济, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人炮叶。 一個月前我還...
    沈念sama閱讀 47,760評論 2 369
  • 正文 我出身青樓碗旅,卻偏偏與公主長得像,于是被迫代替她去往敵國和親镜悉。 傳聞我的和親對象是個殘疾皇子祟辟,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,647評論 2 354

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