iOS runtime 七: 方法查找與動態(tài)決議

快速查找

runtime將方法調(diào)用轉(zhuǎn)換為objc_msgSend函數(shù),盡管每個方法的返回值,參數(shù)可能不一樣,
但是objc_msgSend可以做類型轉(zhuǎn)換.
這個函數(shù)沒有C++實現(xiàn),直接是匯編代碼實現(xiàn).位于.s文件,不同的架構(gòu)有不同的文件以及不同的匯編代碼.
除了objc_msgSend還有幾個相關(guān)的方法objc_msgSendSuper等.
以ENTRY為入口.

        ENTRY _objc_msgSend
    UNWIND _objc_msgSend, NoFrame

    NilTest NORMAL
    GetIsaFast NORMAL       // r10 = self->isa
    // calls IMP on success
    CacheLookup NORMAL, CALL, _objc_msgSend
    NilTestReturnZero NORMAL
    GetIsaSupport NORMAL

// cache miss: go search the method lists
        LCacheMiss_objc_msgSend:
    // isa still in r10
    jmp __objc_msgSend_uncached
//...

這是x86_64的objc_msgSend,
NilTest,GetIsaFast,CacheLookup等等這些都是定義在當前文件中的其他代碼段.
NilTest用于判斷receive是否為空.
GetIsaFast獲取isa
接下來CacheLookup就是快速查找IMP.
如果沒找到,進入__objc_msgSend_uncached

ENTRY _objc_msgSend
UNWIND _objc_msgSend, NoFrame

    cmp p0, #0          // nil check and tagged pointer check
#if SUPPORT_TAGGED_POINTERS
    b.le    LNilOrTagged        //  (MSB tagged pointer looks negative)
#else
    b.eq    LReturnZero
#endif
    ldr p13, [x0]       // p13 = isa
    GetClassFromIsa_p16 p13, 1, x0  // p16 = class
LGetIsaDone:
    // calls imp or objc_msgSend_uncached
    CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached

#if SUPPORT_TAGGED_POINTERS
LNilOrTagged:
    b.eq    LReturnZero     // nil check
    GetTaggedClass
    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

上面這段是arm64的_objc_msgSend.
x86_64沒有SUPPORT_TAGGED_POINTERS的情況,arm64有.
首先也是查看receiver是否為空,如果是空,分成兩種情況,一是tagged pointer,走LNilOrTagged,二是不支持tagged pointer,走LReturnZero.
LNilOrTagged和LReturnZero就在上面代碼的最后部分.

ldr p13, [x0]
GetClassFromIsa_p16 p13, 1, x0
//p13拿到isa 通過isa拿到class放入p16寄存器

CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached
//接下來走CacheLookup

    mov x15, x16            // stash the original isa
LLookupStart\Function:
    // p1 = SEL, p16 = isa
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
    ldr p10, [x16, #CACHE]              // p10 = mask|buckets
    lsr p11, p10, #48           // p11 = mask
    and p10, p10, #0xffffffffffff   // p10 = buckets
    and w12, w1, w11            // x12 = _cmd & mask
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
    ldr p11, [x16, #CACHE]          // p11 = mask|buckets
#if CONFIG_USE_PREOPT_CACHES
#if __has_feature(ptrauth_calls)
    tbnz    p11, #0, LLookupPreopt\Function
    and p10, p11, #0x0000ffffffffffff   // p10 = buckets
#else
    and p10, p11, #0x0000fffffffffffe   // p10 = buckets
    tbnz    p11, #0, LLookupPreopt\Function
#endif
    eor p12, p1, p1, LSR #7
    and p12, p12, p11, LSR #48      // x12 = (_cmd ^ (_cmd >> 7)) & mask
#else
    and p10, p11, #0x0000ffffffffffff   // p10 = buckets
    and p12, p1, p11, LSR #48       // x12 = _cmd & mask
#endif // CONFIG_USE_PREOPT_CACHES
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
    ldr p11, [x16, #CACHE]              // p11 = mask|buckets
    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 p13, p10, p12, LSL #(1+PTRSHIFT)
                        // p13 = buckets + ((_cmd & mask) << (1+PTRSHIFT))

                        // do {
1:  ldp p17, p9, [x13], #-BUCKET_SIZE   //     {imp, sel} = *bucket--
    cmp p9, p1              //     if (sel != _cmd) {
    b.ne    3f              //         scan more
                        //     } else {
2:  CacheHit \Mode              // hit:    call or return imp
                        //     }
3:  cbz p9, \MissLabelDynamic       //     if (sel == 0) goto Miss;
    cmp p13, p10            // } while (bucket >= buckets)
    b.hs    1b

#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
    add p13, p10, w11, UXTW #(1+PTRSHIFT)
                        // p13 = buckets + (mask << 1+PTRSHIFT)
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
    add p13, p10, p11, LSR #(48 - (1+PTRSHIFT))
                        // p13 = buckets + (mask << 1+PTRSHIFT)
                        // see comment about maskZeroBits
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
    add p13, p10, p11, LSL #(1+PTRSHIFT)
                        // p13 = buckets + (mask << 1+PTRSHIFT)
#else
#error Unsupported cache mask storage for ARM64.
#endif
    add p12, p10, p12, LSL #(1+PTRSHIFT)
                        // p12 = first probed bucket

                        // do {
4:  ldp p17, p9, [x13], #-BUCKET_SIZE   //     {imp, sel} = *bucket--
    cmp p9, p1              //     if (sel == _cmd)
    b.eq    2b              //         goto hit
    cmp p9, #0              // } while (sel != 0 &&
    ccmp    p13, p12, #0, ne        //     bucket > first_probed)
    b.hi    4b

這里有一個CACHE_MASK_STORAGE,

#if defined(__arm64__) && __LP64__
#if TARGET_OS_OSX || TARGET_OS_SIMULATOR
#define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
#else
#define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_HIGH_16
#endif
#elif defined(__arm64__) && !__LP64__
#define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_LOW_4
#else
#define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_OUTLINED
#endif

所以arm64真機是CACHE_MASK_STORAGE_HIGH_16

p1的類型是SEL,也就是objc_msgSend傳進來的參數(shù)_cmd, p16是剛才的isa.

ldr p11, [x16, #CACHE] // 這里p11 = mask|buckets,也就是bucket的首地址,

這里CONFIG_USE_PREOPT_CACHES是1,并且我們看帶簽名的部分,也就是__has_feature(ptrauth_calls)是true.

tbnz p11, #0, LLookupPreopt\Function //判斷cache_t存在

and p10, p11, #0x0000fffffffffff //取出后48位,這部分是buckets,從前面的內(nèi)容我們知道bucket是連續(xù)存儲的.

eor p12, p1, p1, LSR #7 //eor異或,LSR #7 是右移7位,這就是前面說到的cache_hash函數(shù)在CONFIG_USE_PREOPT_CACHES為1的時候的算法,是通過這個hash算法獲得SEL的hash值,也就是在buckets中的順位.
上面說到p1是SEL,所以這個就是p12 = SEL ^ (SEL >> 7),在cache_hash函數(shù)里是這么寫的value ^= value >> 7.
and p12, p12, p11, LSR #48 // 不過這里最后還要&上mask,最終得到下標.

add p13, p10, p12, LSL #(1+PTRSHIFT)// 剛才p12是下標,p10是bucket首地址,PTRSHIFT是3,這里是首地址加上下標左移4位.指向了一個bucket內(nèi)存.

1:  ldp p17, p9, [x13], #-BUCKET_SIZE   
    cmp p9, p1          
    b.ne    3f              
2:  CacheHit \Mode          
3:  cbz p9, \MissLabelDynamic       
    cmp p13, p10            
    b.hs    1b

這一段是do while,
ldp p17, p9, [x13], #-BUCKET_SIZE //p13是剛才指向的bucket,-BUCKET_SIZE是一個bucket的大小,意思是指向前面一個bucket.放在p9,
這一步對標cache_next函數(shù).

cmp p9, p1 //如果p9不是_cmd.
b.ne 3f //那么去執(zhí)行3

CacheHit \Mode //p9就是_cmd,就執(zhí)行CacheHit

cbz p9, \MissLabelDynamic //如果p9是空的,執(zhí)行MissLabelDynamic函數(shù).

cmp p13, p10 //如果p13大于p10,也就是說bucket不是第0個.
b.hs 1b //就執(zhí)行1,向前移動.

add p13, p10, p11, LSR #(48 - (1+PTRSHIFT)) //如果向前移動到了首地址,還沒匹配到,那就把p13移動到最后一個bucket

add p12, p10, p12, LSL #(1+PTRSHIFT) //剛才cmp p13, p10是從中間到首地址,現(xiàn)在修改p10,到剛才的p13后面一個,意思就是已經(jīng)查看過的部分就不再查看了

4: ldp p17, p9, [x13], #-BUCKET_SIZE // 取出sel

        cmp p9, p1          
    b.eq    2b              
    cmp p9, #0          
    ccmp    p13, p12, #0, ne    
    b.hi    4b

還是do while,如果相等,就執(zhí)行2, 否則就繼續(xù)移動,直到走到新的首地址.


上面使用了和cache_hash和cache_next相同的算法來查找,也就是存的取的方法是一樣的,只要存了,那一定取的到,
如果沒存,那么在ldp p17, p9, [x13], #-BUCKET_SIZE這一步,p9就是空的,
然后cbz p9, \MissLabelDynamic就會跳轉(zhuǎn)到MissLabelDynamic.
這個MissLabelDynamic是什么呢.

/*
 * CacheLookup NORMAL|GETIMP|LOOKUP <function> MissLabelDynamic MissLabelConstant
*/
.macro CacheLookup Mode, Function, MissLabelDynamic, MissLabelConstant

CacheLookup是這么定義的,MissLabelConstant是參數(shù),是一個函數(shù)

回到前面看objc_msgSend調(diào)用cacheLookup的地方

GetClassFromIsa_p16 p13, 1, x0  // p16 = class
LGetIsaDone:
    // calls imp or objc_msgSend_uncached
    CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached

MissLabelConstant傳的是objc_msgSend_uncached,所以我們?nèi)タ催@個函數(shù)

STATIC_ENTRY __objc_msgSend_uncached
    UNWIND __objc_msgSend_uncached, FrameWithNoSaves
    MethodTableLookup
    TailCallFunctionPointer x17

    END_ENTRY __objc_msgSend_uncached
    STATIC_ENTRY __objc_msgLookup_uncached
    UNWIND __objc_msgLookup_uncached, FrameWithNoSaves
    MethodTableLookup
    ret
    END_ENTRY __objc_msgLookup_uncached

里面執(zhí)行的是MethodTableLookup

.macro MethodTableLookup
    
    SAVE_REGS MSGSEND
    // 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_REGS MSGSEND
.endmacro

MethodTableLookup里面執(zhí)行的是lookUpImpOrForward函數(shù),
傳參是lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER).
這個函數(shù)就是在快速查找失敗的時候會執(zhí)行的,它是C++實現(xiàn).位于objc-runtime-new.mm


還是CacheLookup的定義

#define NORMAL 0
#define GETIMP 1
#define LOOKUP 2
.macro CacheHit
.if $0 == NORMAL
    TailCallCachedImp x17, x10, x1, x16 // authenticate and call imp
.elseif $0 == GETIMP
    mov p0, p17
    cbz p0, 9f          // don't ptrauth a nil imp
    AuthAndResignAsIMP x0, x10, 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, x10, x1, x16    // authenticate imp and re-sign as IMP
    cmp x16, x15
    cinc    x16, x16, ne            // x16 += 1 when x15 != x16 (for instrumentation ; fallback to the parent class)
    ret             // return imp via x17
.else

mode有三種,NORMAL,GETIMP,和LOOKUP
objc_msgSend里面?zhèn)鞯木褪荖ORMAL.是驗證并調(diào)用imp

當傳入GETIMP時,就是_cache_getImp這個函數(shù)的實現(xiàn),

IMP cache_getImp(Class cls, SEL sel, IMP value_on_constant_cache_miss = nil);

這個函數(shù)也是頻繁的被使用.用于獲取imp.

LOOKUP模式是驗證并重簽名imp,不會調(diào)用.

慢速查找

上面講到,快速查找失敗會執(zhí)行l(wèi)ookUpImpOrForward函數(shù),這個函數(shù)位于objc-runtime-new.mm

enum {
    LOOKUP_INITIALIZE = 1,
    LOOKUP_RESOLVER = 2,
    LOOKUP_NIL = 4,
    LOOKUP_NOCACHE = 8,
};

NEVER_INLINE
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
    const IMP forward_imp = (IMP)_objc_msgForward_impcache;
    IMP imp = nil;
    Class curClass;
    runtimeLock.assertUnlocked();
    if (slowpath(!cls->isInitialized())) {
        behavior |= LOOKUP_NOCACHE;
    }

這個方法是在匯編中調(diào)用的時候,傳參是這樣的

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

obj是x0,sel是x1,cls是x2,最后的參數(shù)behavior是x3,傳的是3.

首先判斷類是否初始化isInitialized,這個條件一般不會進來,如果類沒有初始化,behavior = 0011 | 1000 = 1011.

checkIsKnownClass(cls);
    cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);
    runtimeLock.assertLocked();
    curClass = cls;

判斷類是否被添加到類表中,也就是存不存在這個類,如果不存在,就會觸發(fā)斷言.
然后調(diào)用realizeAndInitializeIfNeeded_locked初始化類,behavior & LOOKUP_INITIALIZE是0011 & 0001 或者1011 & 0001總之一定是true.

 for (unsigned attempts = unreasonableClassCount();;) {
        if (curClass->cache.isConstantOptimizedCache(/* strict */true)) {
#if CONFIG_USE_PREOPT_CACHES
            imp = cache_getImp(curClass, sel);
            if (imp) goto done_unlock;
            curClass = curClass->cache.preoptFallbackClass();
#endif
        } else {
            method_t *meth = getMethodNoSuper_nolock(curClass, sel);
            if (meth) {
                imp = meth->imp(false);
                goto done;
            }
            if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
                imp = forward_imp;
                break;
            }
        }
        if (slowpath(--attempts == 0)) {
            _objc_fatal("Memory corruption in class list.");
        }
        imp = cache_getImp(curClass, sel);
        if (slowpath(imp == forward_imp)) {
            break;
        }
        if (fastpath(imp)) {
            // Found the method in a superclass. Cache it in this class.
            goto done;
        }
    }

這是一個最多執(zhí)行attempts次的for循環(huán),或者說是無限循環(huán),當(--attempts == 0)時會直接觸發(fā)斷言.
attempts是一個相當大的數(shù),它是這么實現(xiàn)的

static unsigned unreasonableClassCount()
{
    runtimeLock.assertLocked();
    int base = NXCountMapTable(gdb_objc_realized_classes) +
    getPreoptimizedClassUnreasonableCount();
    return (base + 1) * 16;
}

它是以靜態(tài)類表的大小加上dyld的類的個數(shù)為基礎(chǔ),乘上16得到的一個不真實的數(shù)字,因為那個for循環(huán)不需要真實的循環(huán)次數(shù),只要足夠就行.

isConstantOptimizedCache用于判斷是否緩存優(yōu)化.
CONFIG_USE_PREOPT_CACHES在arm64并且iOS時是1.
cache_getImp前面說到了是cahcelookup的getImp mode,用來查找imp.
如果滿足這些條件,就會走快速查找,不過cahcelookup的getImp mode失敗了不走lookUpImpOrForward,所以不會循環(huán).

另一種情況則是去rw中找method_t, getMethodNoSuper_nolock函數(shù)是從類本身查找.
前面curClass首先指向傳進來的cls.

static method_t *
getMethodNoSuper_nolock(Class cls, SEL sel)
{
    auto const methods = cls->data()->methods();
    for (auto mlists = methods.beginLists(),
              end = methods.endLists();
         mlists != end;
         ++mlists)
    {
        method_t *m = search_method_list_inline(*mlists, sel);
        if (m) return m;
    }
    return nil;
}

從class獲取methods(),這是一個list_array_tt,可能會有兩層結(jié)構(gòu),內(nèi)層結(jié)構(gòu)用search_method_list_inline來迭代.
search_method_list_inline這個函數(shù)在上一篇有說明.

如果從類自己身上沒找到的話,curClass = curClass->getSuperclass(),
curClass會指向父類,順便判斷如果沒有父類了,就賦值imp為forward_imp,這個等下再說.

接下來是前面有提到的for循環(huán)最大執(zhí)行次數(shù).

然后就是從父類查找,如果找到了就跳到done,沒有就繼續(xù)循環(huán).

    if (slowpath(behavior & LOOKUP_RESOLVER)) {
        behavior ^= LOOKUP_RESOLVER;
        return resolveMethod_locked(inst, sel, cls, behavior);
    }
 done:
    if (fastpath((behavior & LOOKUP_NOCACHE) == 0)) {
#if CONFIG_USE_PREOPT_CACHES
        while (cls->cache.isConstantOptimizedCache(/* strict */true)) {
            cls = cls->cache.preoptFallbackClass();
        }
#endif
        log_and_fill_cache(cls, imp, sel, inst, curClass);
    }
 done_unlock:
    runtimeLock.unlock();
    if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
        return nil;
    }
    return imp;

如果for循環(huán)中找到了imp,就會跳轉(zhuǎn)到done或者done_unlock,
如果沒有找到imp,但是從for循環(huán)break了,就會走resolveMethod_locked.
但是它是有條件的,為什么要設(shè)置條件呢,因為resolveMethod_locked的流程中也會調(diào)用lookUpImpOrForward,
這行限制改變了behavior,第二次進來的時候就不滿足條件了.

static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
    if (! cls->isMetaClass()) {
        resolveInstanceMethod(inst, sel, cls);
    } 
    else {
        resolveClassMethod(inst, sel, cls);
        if (!lookUpImpOrNilTryCache(inst, sel, cls)) {
            resolveInstanceMethod(inst, sel, cls);
        }
    }
    return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);
}

這個過程叫做動態(tài)方法決議.
做了兩件事,
1.如果是元類就調(diào)用resolveClassMethod,如果是類對象就調(diào)用resolveInstanceMethod
2.調(diào)用lookUpImpOrForwardTryCache
lookUpImpOrForwardTryCache就是_lookUpImpTryCache,它里面干了兩件事,
一是快速查找,二是慢速查找.
為什么要再來次,這是因為步驟1,也就是動態(tài)決議,給了程序一個臨時添加方法的機會,添加之后,再走一次查找流程.

static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
    SEL resolve_sel = @selector(resolveInstanceMethod:);
    if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) {
        return;
    }
    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(cls, resolve_sel, sel);
    IMP imp = lookUpImpOrNilTryCache(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));
        }
    }
}

首先獲取了一個SEL,resolveInstanceMethod,并且判斷class是否實現(xiàn)了這個sel.
如果實現(xiàn)了,就給class發(fā)消息,調(diào)用resolveInstanceMethod這個方法.
然后調(diào)用lookUpImpOrNilTryCache查詢一次sel是否存在了.

void newFunc(){
    NSLog(@"新添加的method6");
}

+ (BOOL)resolveInstanceMethod:(SEL)sel{
    NSLog(@"未知的方法 %@",NSStringFromSelector(sel));
    if([NSStringFromSelector(sel) isEqualToString:@"myMethod6"]){
        class_addMethod(self, sel, (IMP)newFunc, "");
        return true;
    }
    return [super resolveInstanceMethod:sel];
}

//main.m
id myObj = [MyClass alloc];
MyClass *my = (MyClass *)[myObj init];
[my performSelector:NSSelectorFromString(@"myMethod6")];

寫一個demo,可以通過在bool resolved = msg(cls, resolve_sel, sel);斷點,
看到消息發(fā)送之后,輸出了"未知的方法 myMethod6".
放開斷點輸出"新添加的method6".


在done的位置還調(diào)用了log_and_fill_cache函數(shù)

log_and_fill_cache(Class cls, IMP imp, SEL sel, id receiver, Class implementer)
{
#if SUPPORT_MESSAGE_LOGGING
    if (slowpath(objcMsgLogEnabled && implementer)) {
        bool cacheIt = logMessageSend(implementer->isMetaClass(), 
                                      cls->nameForLogging(),
                                      implementer->nameForLogging(), 
                                      sel);
        if (!cacheIt) return;
    }
#endif
    cls->cache.insert(sel, imp, receiver);
}

這個函數(shù)的作用是給cache_t添加數(shù)據(jù).這部分的內(nèi)容在上一篇.
上面的logMessageSend是輸出方法調(diào)用的信息.

void instrumentObjcMessageSends(BOOL flag)

通過這個函數(shù)可以設(shè)置是否輸出.


前面提到了一個forward_imp.

const IMP forward_imp = (IMP)_objc_msgForward_impcache;

_objc_msgForward_impcache沒有c++的實現(xiàn),但是可以在.s里找到匯編實現(xiàn)

STATIC_ENTRY __objc_msgForward_impcache

    // No stret specialization.
    b   __objc_msgForward

    END_ENTRY __objc_msgForward_impcache

ENTRY __objc_msgForward

    adrp    x17, __objc_forward_handler@PAGE
    ldr p17, [x17, __objc_forward_handler@PAGEOFF]
    TailCallFunctionPointer x17
    
    END_ENTRY __objc_msgForward
``
里面就一句,執(zhí)行__objc_msgForward,然后__objc_msgForward的實現(xiàn)緊接著在下面,
調(diào)用__objc_forward_handler,最終返回一個x17.

__objc_forward_handler又回到了c++代碼

// Default forward handler halts the process.
attribute((noreturn, cold)) void
objc_defaultForwardHandler(id self, SEL sel)
{
_objc_fatal("%c[%s %s]: unrecognized selector sent to instance %p "
"(no message forward handler is installed)",
class_isMetaClass(object_getClass(self)) ? '+' : '-',
object_getClassName(self), sel_getName(sel), self);
}
void _objc_forward_handler = (void)objc_defaultForwardHandler;

這就是轉(zhuǎn)發(fā)的默認實現(xiàn),會直接終止程序,錯誤信息unrecognized selector sent to instance ...
老朋友了屬于是.

這個函數(shù)是可以修改的.

void objc_setForwardHandler(void *fwd, void *fwd_stret)
{
_objc_forward_handler = fwd;

if SUPPORT_STRET

_objc_forward_stret_handler = fwd_stret;

endif

}

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

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

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