iOS runtime 部分三

主要探究 objc_msgSend()的流程;

文中使用的 objc4源碼是objc-781版本;

runtime 部分一
runtime 部分二
runtime 部分三


1. objc_msgSend()的流程;

首先我們都知道, 我們調(diào)用方法后到底層就是通過(guò)objc_msgSend()來(lái)實(shí)現(xiàn)的, 這個(gè)機(jī)制就是我們所說(shuō)的消息發(fā)送機(jī)制, 這個(gè)過(guò)程分為消息發(fā)送 動(dòng)態(tài)解析 消息轉(zhuǎn)發(fā)三個(gè)階段;
轉(zhuǎn)化為objc_msgSend()有兩個(gè)參數(shù), 第一個(gè)是接收者receiver, 第二個(gè)是SEL;

///不論是類方法還是實(shí)例方法底層都是objc_msgSend()方式實(shí)現(xiàn);
Person *person = [[Person alloc] init];
[person realizedMehod];
///轉(zhuǎn)化為C++后代碼如下
Person *person = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("realizedMehod"));

由于 objc_msgSend()的實(shí)現(xiàn)過(guò)程是通過(guò)匯編實(shí)現(xiàn), 本人能力有限, 只能大致推測(cè)下, 從 objc_msgSend()開(kāi)始的整個(gè)流程, 如果有;理解錯(cuò)誤的地方還請(qǐng)不吝賜教;
注意匯編中調(diào)用 C 或者 C++的函數(shù)會(huì)在函數(shù)名之前加上_, 例如objc_msgSend()在匯編中對(duì)應(yīng)的就是_objc_msgSend;

///入口
/********************************************************************
 *
 * id objc_msgSend(id self, SEL _cmd, ...);
 * IMP objc_msgLookup(id self, SEL _cmd, ...);
 * 
 * objc_msgLookup ABI:
 * IMP returned in x17
 * x16 reserved for our use but not used
 *
 ********************************************************************/
....
        //_objc_msgSend開(kāi)始
    ENTRY _objc_msgSend
    UNWIND _objc_msgSend, NoFrame
    cmp p0, #0          // nil check and tagged pointer check
///在 arm64架構(gòu)下SUPPORT_TAGGED_POINTERS = 1 , 所以下一步LNilOrTagged, 檢查 receiver 是否為空;
#if SUPPORT_TAGGED_POINTERS
    b.le    LNilOrTagged        //  (MSB tagged pointer looks negative)
#else
    b.eq    LReturnZero
#endif
    ldr p13, [x0]       // p13 = isa
        ///通過(guò) isa 獲取 class
    GetClassFromIsa_p16 p13     // p16 = class
LGetIsaDone:
    // calls imp or objc_msgSend_uncached
        ///開(kāi)始緩存查找, 注意入?yún)⑹荖ORMAL
    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

===>
///緩存查找
.macro CacheLookup
LLookupStart$1:
    // p1 = SEL, p16 = isa
    ldr p11, [x16, #CACHE]              // p11 = mask|buckets
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
        /*
          arm64架構(gòu)執(zhí)行這里的邏輯;
          雖然我們不知道匯編的具體作用, 但是從注釋依然可以看出一些信息. 從散列表(buckets)中
          通過(guò)_cmd & mask來(lái)獲取方法 IMP, 這個(gè)跟之前的方法緩存的算法和過(guò)程相對(duì)應(yīng);
        */
    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

...

#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
      ///找到緩存 call or return imp
    CacheHit $0         // call or return imp
    
2:  // not hit: p12 = not-hit bucket
      ///緩存沒(méi)有找到,  調(diào)用CheckMiss
    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

===>
///宏定義CheckMiss
.macro CheckMiss
    // miss if bucket->sel == 0
.if $0 == GETIMP
    cbz p9, LGetImpMiss
///之前的入?yún)⑹荖ORMAL, 下一步調(diào)用__objc_msgSend_uncached
.elseif $0 == NORMAL
    cbz p9, __objc_msgSend_uncached
.elseif $0 == LOOKUP
    cbz p9, __objc_msgLookup_uncached
.else
.abort oops
.endif
.endmacro

===>
///__objc_msgSend_uncached的流程
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
///調(diào)用MethodTableLookup
MethodTableLookup
TailCallFunctionPointer x17
END_ENTRY __objc_msgSend_uncached

===>

.macro MethodTableLookup
...
// lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER)
// receiver and selector already in x0 and x1
mov x2, x16
mov x3, #3
///調(diào)用lookUpImpOrForward方法
bl  _lookUpImpOrForward
...

下面就是到源碼lookUpImpOrForward的實(shí)現(xiàn);
===>

//**********************************************************************************************//
在arm64-asm.h文件中可以得知在 arm64 & __LP64__模式下SUPPORT_TAGGED_POINTERS = 1
#if __arm64__
#if __LP64__
// true arm64
#define SUPPORT_TAGGED_POINTERS 1
....
===
在objc-config.h文件中我們可以得知在 arm64 &  __LP64__模式下 CACHE_MASK_STORAGE = CACHE_MASK_STORAGE_HIGH_1
#define CACHE_MASK_STORAGE_OUTLINED 1
#define CACHE_MASK_STORAGE_HIGH_16 2
#define CACHE_MASK_STORAGE_LOW_4 3
#if defined(__arm64__) && __LP64__
#define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_HIGH_16
#elif defined(__arm64__) && !__LP64__
#define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_LOW_4
#else
#define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_OUTLINED
#endif
#endif
//**********************************************************************************************//

上面的過(guò)程主要是記錄下在匯編層面objc_msgSend()的流程, 下面著重說(shuō)下lookUpImpOrForward的實(shí)現(xiàn);

/***********************************************************************
* lookUpImpOrForward.
* The standard IMP lookup. 
標(biāo)準(zhǔn)的 IMP 查找方法
...
*   If you don't want forwarding at all, use LOOKUP_NIL.
注意這句話, 如果你完全不想使用消息轉(zhuǎn)發(fā), 則使用LOOKUP_NIL
**********************************************************************/
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
    ///獲取消息轉(zhuǎn)發(fā)的 IMP
    const IMP forward_imp = (IMP)_objc_msgForward_impcache;
     ///初始的imp為空
    IMP imp = nil;
    ///當(dāng)前類
    Class curClass;
    ///runtime鎖
    runtimeLock.assertUnlocked();

    // Optimistic cache lookup
      ///查找方法緩存, 如果查找到緩存直接結(jié)束流程將方法返回
    if (fastpath(behavior & LOOKUP_CACHE)) {
        imp = cache_getImp(cls, sel);
        if (imp) goto done_nolock;
    }  
    ///檢查類的initialize相關(guān)
     runtimeLock.lock();
     checkIsKnownClass(cls);
    if (slowpath(!cls->isRealized())) {
        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.assertLocked();
    curClass = cls;

    ///核心方法, 通過(guò)向上遍歷當(dāng)前類的父類, 來(lái)查找imp
    for (unsigned attempts = unreasonableClassCount();;) {
        // curClass method list.
        ///獲取當(dāng)前類的方法列表, 不查詢父類
        Method meth = getMethodNoSuper_nolock(curClass, sel);
        ///如果當(dāng)前類中查找到imp則結(jié)束查找 去done:出緩存imp然后返回這個(gè)imp;
        if (meth) {
            imp = meth->imp;
            goto done;
        }
         /*
          注意這個(gè)地方的寫法, 執(zhí)行完這句代碼后, curClass就已經(jīng)指向父類了;
          if (slowpath((curClass = curClass->superclass) == nil)) {}
          等同于下面兩句語(yǔ)句組合, 如果有疑問(wèn)可以自行測(cè)試下;
          curClass = curClass->superclass
          if (slowpath(curClass  == nil) {}
        
        如果查詢到基類仍然沒(méi)有查找大相關(guān)方法, 則使用消息轉(zhuǎn)發(fā)(注意這里只是跳出循環(huán))
        實(shí)際上下面要先判斷動(dòng)態(tài)解析, 動(dòng)態(tài)解析是先于消息轉(zhuǎn)發(fā)的;
         */
        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.
        if (slowpath(--attempts == 0)) {
            _objc_fatal("Memory corruption in class list.");
        }

        // Superclass cache.
        /*
        從curClass已經(jīng)指向了父類, 所以這里判斷父類中是否有消息轉(zhuǎn)發(fā), 
        如果子類沒(méi)有消息轉(zhuǎn)發(fā)相關(guān)處理, 寫在父類中實(shí)現(xiàn)消息轉(zhuǎn)發(fā)也會(huì)有效;
        仍然是跳出循環(huán), 先去動(dòng)態(tài)解析;
        */
        imp = cache_getImp(curClass, sel);
        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;
        }  
        //如果父類中方法緩存中查找到了, 則將方法緩存到本類然后返回imp
        if (fastpath(imp)) {
            // Found the method in a superclass. Cache it in this class.
            goto done;
        }
    }

    // No implementation found. Try method resolver once.
    /*  
      如果沒(méi)有找到IMP則開(kāi)始動(dòng)態(tài)解析, 不論是否成功最后都會(huì)再次調(diào)用lookUpImpOrForward;
      注意只進(jìn)行一次動(dòng)態(tài)解析, 如果動(dòng)態(tài)解析成功, 則將相關(guān)方法緩存到本類;
      下次再次調(diào)用時(shí)則是直接查找類中方法列表即可;
      如果動(dòng)態(tài)解析失敗, 則再次lookUpImpOrForward, 重新開(kāi)始流程, 進(jìn)入消息轉(zhuǎn)發(fā)階段;
      具體請(qǐng)看resolveMethod_locked的官方注釋和實(shí)現(xiàn)流程;
     */
    if (slowpath(behavior & LOOKUP_RESOLVER)) {
        behavior ^= LOOKUP_RESOLVER;
        return resolveMethod_locked(inst, sel, cls, behavior);
    }

 done:
    ///將查找到的imp緩存
    log_and_fill_cache(cls, imp, sel, inst, curClass);
    runtimeLock.unlock();
 done_nolock:
    ///如果完全不想使用消息轉(zhuǎn)發(fā), 但是獲取到的緩存imp=forward_imp則直接返回nil
    if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
        return nil;
    }
    ///最終返回imp
    return imp;
}


//******************************************************************//
///動(dòng)態(tài)解析方法入口
static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior) {
    runtimeLock.assertLocked();
    ASSERT(cls->isRealized());
    runtimeLock.unlock();
    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
    return lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE);
}
===>
///以實(shí)例方法為例, 動(dòng)態(tài)解析方法入口中會(huì)調(diào)用  resolveInstanceMethod方法

/***********************************************************************
* resolveInstanceMethod
* Call +resolveInstanceMethod, looking for a method to be added to class cls.
   ///尋找要添加到類中的method;
* cls may be a metaclass or a non-meta class.
* Does not check if the method already exists.
**********************************************************************/
static void resolveInstanceMethod(id inst, SEL sel, Class cls) {
    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    SEL resolve_sel = @selector(resolveInstanceMethod:);
    if (!lookUpImpOrNil(cls, resolve_sel, cls->ISA())) {
        // Resolver not implemented.
        return;
    }
    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(cls, resolve_sel, sel);
    // Cache the result (good or bad) so the resolver doesn't fire next time.
    //不論好壞將結(jié)果緩存起來(lái), 動(dòng)態(tài)解析不會(huì)再觸發(fā)
    // +resolveInstanceMethod adds to self a.k.a. cls
    IMP imp = lookUpImpOrNil(inst, sel, cls);
 ...
}

至此objc_msgSend()的流程大致結(jié)束, 下面代碼測(cè)試下動(dòng)態(tài)解析和消息轉(zhuǎn)發(fā);

2. 動(dòng)態(tài)解析

兩個(gè)關(guān)鍵方法: 實(shí)例方法+(BOOL)resolveInstanceMethod: ; 類方法+(BOOL)resolveClassMethod:
以實(shí)例方法為例, 簡(jiǎn)單的理解為:
調(diào)用notRealizeMethod方法后后, 如果本類或者父類實(shí)現(xiàn)了這個(gè)方法則調(diào)用, 否則檢查是否實(shí)現(xiàn)動(dòng)態(tài)解析方法,
實(shí)現(xiàn)了resolveInstanceMethod方法,開(kāi)始動(dòng)態(tài)解析; 這個(gè)地方就是動(dòng)態(tài)為本類添加一個(gè)方法去映射實(shí)現(xiàn)notRealizeMethod;
沒(méi)有實(shí)現(xiàn)resolveInstanceMethod, 程序crash拋出unrecognized selector sent to instance;
開(kāi)始動(dòng)態(tài)解析;

調(diào)用未實(shí)現(xiàn)的方法notRealizeMethod
Cat *cat = [[Cat alloc] init];
[cat notRealizeMethod];
///Cat的實(shí)現(xiàn)如下;
//.h文件
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface Cat : NSObject
///此方法未實(shí)現(xiàn)
- (void)notRealizeMethod;
@end
NS_ASSUME_NONNULL_END
///.m文件為
#import "Cat.h"
#import <objc/runtime.h>
@implementation Cat
- (void)HandleNotRealizedMethod {
    NSLog(@"HandleNotRealizedMethod : %s", __func__);
}
/*
 對(duì)notRealizeMethod方法, 沒(méi)有實(shí)現(xiàn), 調(diào)用后;
 實(shí)現(xiàn)了resolveInstanceMethod方法,開(kāi)始動(dòng)態(tài)解析;
 沒(méi)有實(shí)現(xiàn)resolveInstanceMethod, 程序crash拋出unrecognized selector sent to instance;
 開(kāi)始動(dòng)態(tài)解析
 */
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel  == @selector(notRealizeMethod)) {
        SEL handelSel = @selector(HandleNotRealizedMethod);
        Method handleMethod = class_getInstanceMethod(self, handelSel);
        IMP  imp  = class_getMethodImplementation(self, handelSel);
        /*
         為某個(gè)類添加Method;
         參數(shù)1: 為哪個(gè)類添加方法;
         參數(shù)2: 為哪個(gè)方法添加實(shí)現(xiàn);
         參數(shù)3: 方法的具體實(shí)現(xiàn);
         參數(shù)4: 方法的編碼格式;
         */
        class_addMethod(self, sel, imp, method_getTypeEncoding(handleMethod));
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}
@end
///運(yùn)行后的結(jié)果為
2020-07-04 17:36:59.568045+0800 objc_msgSend[11787:194063] HandleNotRealizedMethod : -[Cat HandleNotRealizedMethod]

3. 消息轉(zhuǎn)發(fā)

如果動(dòng)態(tài)解析方法沒(méi)有實(shí)現(xiàn), 或者沒(méi)有處理動(dòng)態(tài)解析, 則進(jìn)入消息轉(zhuǎn)發(fā)階段;
以實(shí)例方法為例, 大致流程為:
幾個(gè)主要的方法;
- (id)forwardingTargetForSelector:()返回一個(gè)能處理notRealizeInstanceMethod的對(duì)象;
- (NSMethodSignature *)methodSignatureForSelector:()方法簽名;
- (void)forwardInvocation:()最終的處理

注意: 類方法也有消息轉(zhuǎn)發(fā);
把相關(guān)的方法打出后手動(dòng)改為+即可;處理的流程跟實(shí)例方法類似;
+ (id)forwardingTargetForSelector:();
+ (NSMethodSignature *)methodSignatureForSelector:();
+ (void)forwardInvocation:();

測(cè)試代碼

///調(diào)用方法
Pig *pig = [[Pig alloc] init];
[pig notRealizeInstanceMethod];
[Pig notRealizeClassMethod];
   

///動(dòng)態(tài)解析階段, 不處理或者處理不成功, 進(jìn)入消息轉(zhuǎn)發(fā)階段
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    return [super resolveInstanceMethod:sel];
}

/*
 aSelector這個(gè)時(shí)候就是notRealizeInstanceMethod;
 這個(gè)地方需要返回一個(gè)值, 就是返回一個(gè)能處理notRealizeInstanceMethod的對(duì)象;
 例如Piggy也有實(shí)例方法notRealizeInstanceMethod, 并且也實(shí)現(xiàn)了,這時(shí)可以返回Piggy的實(shí)例對(duì)象;即可處理方法;
 */
- (id)forwardingTargetForSelector:(SEL)aSelector {
//如過(guò)將下面注釋的代碼打開(kāi), 則直接將消息轉(zhuǎn)發(fā)給Piggy的實(shí)例對(duì)象, 方法簽名的相關(guān)方法不再生效;
    ///如果發(fā)現(xiàn)Pig對(duì)象調(diào)用notRealizeInstanceMethod方法, 方法沒(méi)有實(shí)現(xiàn), 并且動(dòng)態(tài)解析失敗, 則將消息轉(zhuǎn)發(fā)給Piggy對(duì)象;  
//    if (aSelector == @selector(notRealizeInstanceMethod)) {
//        Piggy *piggy =  [[Piggy alloc] init];
//        /*
//         消息轉(zhuǎn)發(fā)的過(guò)程, 源碼不開(kāi)源, 并不能找到相關(guān)流程, 但是消息轉(zhuǎn)發(fā)后有做一個(gè)操作就是objc_msgSend( piggy,  aSelector);
//         就是讓piggy調(diào)用aSelector;
//         */
//        return piggy;
//    }
    return [super forwardingTargetForSelector:aSelector];
}


/*
 如果forwardingTargetForSelector并不能返回一個(gè)有效的對(duì)象; 開(kāi)始進(jìn)入方法簽名階段
 */
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    /*
     方法編碼, 要返回aSelector的編碼格式;
     如果返回一個(gè)合理的值, 則調(diào)用forwardInvocation方法;
     */
    NSMethodSignature *sig = [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
    return sig;
}


/*
 到了這一步, 可以做任何操作了
 類似KVC是實(shí)現(xiàn)了setValue: forUndefinedKey:即使沒(méi)有相應(yīng)key, 實(shí)現(xiàn)了此方法, 什么都不做也不會(huì)崩潰;
 
 NSInvocation封裝了一個(gè)方法的調(diào)用信息; 調(diào)用者, 方法, 方法編碼;
 target: 方法之前的調(diào)用者, 可更改為其他調(diào)用者;
 selector : 需要調(diào)用的方法, 可更改為其他方法;
 methodSignature : 方法的簽名信息; 不可更改;

 只要target和selector是配套合理的,methodSignature可以忽略;例如:
 anInvocation.target = [Dog class];
 anInvocation.selector = @selector(classTest:);
 [anInvocation invoke];
 */
-  (void)forwardInvocation:(NSInvocation *)anInvocation {
    anInvocation.target = [Dog class];
    anInvocation.selector = @selector(classTest:);
    [anInvocation invoke];
 }

以類方法為例驗(yàn)證另一個(gè)問(wèn)題: 子類中調(diào)用沒(méi)有實(shí)現(xiàn)方法, 且沒(méi)有做方法的消息轉(zhuǎn)發(fā), 但是父類實(shí)現(xiàn)了消息轉(zhuǎn)發(fā), 也會(huì)有效;

//調(diào)用方法
[Pig notRealizeClassMethod];

///Pig的父類Animal的.m實(shí)現(xiàn)為
#import "Animal.h"
@implementation Animal
+  (id)forwardingTargetForSelector:(SEL)aSelector {
    return [super forwardingTargetForSelector:aSelector];
}
+  (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    NSMethodSignature *sig = [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
    return sig;
}
+ (void)forwardInvocation:(NSInvocation *)anInvocation {
    NSLog(@"Pig類中沒(méi)有實(shí)現(xiàn)notRealizeClassMethod方法, 且沒(méi)有做類方法的消息轉(zhuǎn)發(fā), 但是父類實(shí)現(xiàn)了消息轉(zhuǎn)發(fā), 也會(huì)有效;");
}
@end

2020-07-04 18:16:20.022633+0800 objc_msgSend[15196:261753] Pig類中沒(méi)有實(shí)現(xiàn)notRealizeClassMethod方法, 
且沒(méi)有做類方法的消息轉(zhuǎn)發(fā), 但是父類實(shí)現(xiàn)了消息轉(zhuǎn)發(fā), 也會(huì)有效;

4. 流程圖總結(jié)消息發(fā)送, 動(dòng)態(tài)解析, 消息轉(zhuǎn)發(fā)的過(guò)程

  • 消息發(fā)送階段的流程:


  • 動(dòng)態(tài)解析階段流程:


    image.png
  • 消息轉(zhuǎn)發(fā)流程
    類方法的處理流程類似, 把相關(guān)的-號(hào)方法換成+號(hào)方法;


文中測(cè)試代碼


參考文章和下載鏈接
Apple 一些源碼的下載地址
方法的查找順序
什么是散列表
LP64 結(jié)構(gòu)數(shù)據(jù)占據(jù)多少位
LP64什么意思
匯編和 C 函數(shù)的相互調(diào)用
iOS 方法簽名機(jī)制
iOS方法返回值和參數(shù)對(duì)應(yīng)的Type Encodings

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子弹澎,更是在濱河造成了極大的恐慌,老刑警劉巖怖亭,帶你破解...
    沈念sama閱讀 221,820評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡谈跛,警方通過(guò)查閱死者的電腦和手機(jī)攀细,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門箫踩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人谭贪,你說(shuō)我怎么就攤上這事境钟。” “怎么了俭识?”我有些...
    開(kāi)封第一講書人閱讀 168,324評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵慨削,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng)缚态,這世上最難降的妖魔是什么磁椒? 我笑而不...
    開(kāi)封第一講書人閱讀 59,714評(píng)論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮玫芦,結(jié)果婚禮上浆熔,老公的妹妹穿的比我還像新娘。我一直安慰自己桥帆,他們只是感情好医增,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,724評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著老虫,像睡著了一般叶骨。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上张遭,一...
    開(kāi)封第一講書人閱讀 52,328評(píng)論 1 310
  • 那天邓萨,我揣著相機(jī)與錄音,去河邊找鬼菊卷。 笑死缔恳,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的洁闰。 我是一名探鬼主播歉甚,決...
    沈念sama閱讀 40,897評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼扑眉!你這毒婦竟也來(lái)了纸泄?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 39,804評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤腰素,失蹤者是張志新(化名)和其女友劉穎聘裁,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體弓千,經(jīng)...
    沈念sama閱讀 46,345評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡衡便,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,431評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了洋访。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片镣陕。...
    茶點(diǎn)故事閱讀 40,561評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖姻政,靈堂內(nèi)的尸體忽然破棺而出呆抑,到底是詐尸還是另有隱情,我是刑警寧澤汁展,帶...
    沈念sama閱讀 36,238評(píng)論 5 350
  • 正文 年R本政府宣布鹊碍,位于F島的核電站厌殉,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏侈咕。R本人自食惡果不足惜年枕,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,928評(píng)論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望乎完。 院中可真熱鬧,春花似錦品洛、人聲如沸树姨。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 32,417評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)帽揪。三九已至,卻和暖如春辅斟,著一層夾襖步出監(jiān)牢的瞬間转晰,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,528評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工士飒, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留查邢,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,983評(píng)論 3 376
  • 正文 我出身青樓酵幕,卻偏偏與公主長(zhǎng)得像扰藕,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子芳撒,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,573評(píng)論 2 359