跳转至

消息发送和消息转发机制

objc_msgSend

方法的本质是消息

消息发送:通过sel找imp

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        // 编译 上层 代码 得到一个解释
        // 调用方法 = 消息发送 : objc_msgSend(消息的接受者,消息的主体(sel + 参数))

        LGPerson *person = [LGPerson alloc];
        LGTeacher *teach = [LGTeacher alloc];

        [person sayNB];
//        objc_msgSend(person, @selector(sayNB));

        // 子类是不是没有 -> 父类
        [person sayHello];
    }
    return 0;
}

clang -rewrite-objc main.m,生成一个.cpp文件

底层代码

调用方法 = 消息发送:objc_msgSend(消息接收者, 消息的方法名sel, 方法的参数);

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        //类方法                    
        LGPerson *person = ((LGPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LGPerson"), sel_registerName("alloc"));
        LGTeacher *teach = ((LGTeacher *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LGTeacher"), sel_registerName("alloc"));

        //实例方法                    
        ((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("sayNB"));

        ((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("sayHello"));
    }
    return 0;
}

objc_msgSendSuper

从当前类的父类中查找方法的实现:

objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)

方法的快速查找

objc_msgSend

在OC中,所有的消息调用最后都会通过 objc_msgSend 方法进行访问。为了加快执行速度,这个方法在runtime源码中是用汇编实现的。参数在寄存器中。

汇编会在函数前面加一个下划线,为了防止符号(汇编和C语言符号)冲突。

文件位置:objc-msg-arm64.sarm64是真机。

//进入objc_msgSend流程
    ENTRY _objc_msgSend
//流程开始,无需frame
    UNWIND _objc_msgSend, NoFrame

//判断p0(消息接受者)是否存在,不存在则重新开始执行objc_msgSend
    cmp p0, #0          // nil check and tagged pointer check

//如果支持小对象类型。返回小对象或空
#if SUPPORT_TAGGED_POINTERS
//b是进行跳转,b.le是小于判断,也就是小于的时候LNilOrTagged
    b.le    LNilOrTagged        //  (MSB tagged pointer looks negative)
#else
//等于,如果不支持小对象,就LReturnZero
    b.eq    LReturnZero
#endif
//通过p13取isa
    ldr p13, [x0]       // p13 = isa
//通过isa取class并保存到p16寄存器中
    GetClassFromIsa_p16 p13, 1, x0  // p16 = class
//LGetIsaDone是一个入口
LGetIsaDone:
    // calls imp or objc_msgSend_uncached
//进入到缓存查找或者没有缓存查找方法的流程
    CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached

#if SUPPORT_TAGGED_POINTERS
LNilOrTagged:
// nil check判空处理,直接退出
    b.eq    LReturnZero     // nil check
    GetTaggedClass
    b   LGetIsaDone
// SUPPORT_TAGGED_POINTERS
#endif

CacheLookup

//在cache中通过sel查找imp的核心流程
.macro CacheLookup Mode, Function, MissLabelDynamic, MissLabelConstant

//从x16中取出class移到x15中
    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表示p16寄存器存储的值,当前是Class
//#数值表示一个值,这里的CACHE经过全局搜索发现是2倍的指针地址,也就是16个字节
//#define CACHE (2 * __SIZEOF_POINTER__)
//经计算,p10就是cache
    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
//真机64位看这个
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
//CACHE 16字节,也就是通过isa内存平移获取cache,然后cache的首地址就是 (bucket_t *)
    ldr p11, [x16, #CACHE]          // p11 = mask|buckets
#if CONFIG_USE_PREOPT_CACHES
//获取buckets
#if __has_feature(ptrauth_calls)
    tbnz    p11, #0, LLookupPreopt\Function
    and p10, p11, #0x0000ffffffffffff   // p10 = buckets
#else
//and表示与运算,将与上mask后的buckets值保存到p10寄存器
    and p10, p11, #0x0000fffffffffffe   // p10 = buckets
//p11与#0比较,如果p11不存在,就走Function,如果存在走LLookupPreopt
    tbnz    p11, #0, LLookupPreopt\Function
#endif
//按位右移7个单位,存到p12里面,p0是对象,p1是_cmd
    eor p12, p1, p1, LSR #7
    and p12, p12, p11, LSR #48      // x12 = (_cmd ^ (_cmd >> 7)) & mask
#else
    and p10, p11, #0x0000ffffffffffff   // p10 = buckets
//LSR表示逻辑向右偏移
//p11, LSR #48表示cache偏移48位,拿到前16位,也就是得到mask
//这个是哈希算法,p12存储的就是搜索下标(哈希地址)
//整句表示_cmd & mask并保存到p12
    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

//去除掩码后bucket的内存平移
//PTRSHIFT经全局搜索发现是3
//LSL #(1+PTRSHIFT)表示逻辑左移4位,也就是*16
//通过bucket的首地址进行左平移下标的16倍数并与p12相与得到bucket,并存入到p13中
    add p13, p10, p12, LSL #(1+PTRSHIFT)
                        // p13 = buckets + ((_cmd & mask) << (1+PTRSHIFT))

                        // do {

//ldp表示出栈,取出bucket中的imp和sel分别存放到p17和p9
1:  ldp p17, p9, [x13], #-BUCKET_SIZE   //     {imp, sel} = *bucket--
//cmp表示比较,对比p9和p1,如果相同就找到了对应的方法,返回对应imp,走CacheHit
    cmp p9, p1              //     if (sel != _cmd) {
//b.ne表示如果不相同则跳转到2f
    b.ne    3f              //         scan more
                        //     } else {
2:  CacheHit \Mode              // hit:    call or return imp
                        //     }
//向前查找下一个bucket,一直循环直到找到对应的方法,循环完都没有找到就调用_objc_msgSend_uncached
3:  cbz p9, \MissLabelDynamic       //     if (sel == 0) goto Miss;
//通过p13和p10来判断是否是第一个bucket
    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

LLookupEnd\Function:
LLookupRecover\Function:
    b   \MissLabelDynamic

#if CONFIG_USE_PREOPT_CACHES
#if CACHE_MASK_STORAGE != CACHE_MASK_STORAGE_HIGH_16
#error config unsupported
#endif
LLookupPreopt\Function:
#if __has_feature(ptrauth_calls)
    and p10, p11, #0x007ffffffffffffe   // p10 = buckets
    autdb   x10, x16            // auth as early as possible
#endif

    // x12 = (_cmd - first_shared_cache_sel)
    adrp    x9, _MagicSelRef@PAGE
    ldr p9, [x9, _MagicSelRef@PAGEOFF]
    sub p12, p1, p9

    // w9  = ((_cmd - first_shared_cache_sel) >> hash_shift & hash_mask)
#if __has_feature(ptrauth_calls)
    // bits 63..60 of x11 are the number of bits in hash_mask
    // bits 59..55 of x11 is hash_shift

    lsr x17, x11, #55           // w17 = (hash_shift, ...)
    lsr w9, w12, w17            // >>= shift

    lsr x17, x11, #60           // w17 = mask_bits
    mov x11, #0x7fff
    lsr x11, x11, x17           // p11 = mask (0x7fff >> mask_bits)
    and x9, x9, x11         // &= mask
#else
    // bits 63..53 of x11 is hash_mask
    // bits 52..48 of x11 is hash_shift
    lsr x17, x11, #48           // w17 = (hash_shift, hash_mask)
    lsr w9, w12, w17            // >>= shift
    and x9, x9, x11, LSR #53        // &=  mask
#endif

    // sel_offs is 26 bits because it needs to address a 64 MB buffer (~ 20 MB as of writing)
    // keep the remaining 38 bits for the IMP offset, which may need to reach
    // across the shared cache. This offset needs to be shifted << 2. We did this
    // to give it even more reach, given the alignment of source (the class data)
    // and destination (the IMP)
    ldr x17, [x10, x9, LSL #3]      // x17 == (sel_offs << 38) | imp_offs
    cmp x12, x17, LSR #38

.if \Mode == GETIMP
    b.ne    \MissLabelConstant      // cache miss
    sbfiz x17, x17, #2, #38         // imp_offs = combined_imp_and_sel[0..37] << 2
    sub x0, x16, x17                // imp = isa - imp_offs
    SignAsImp x0
    ret
.else
    b.ne    5f                      // cache miss
    sbfiz x17, x17, #2, #38         // imp_offs = combined_imp_and_sel[0..37] << 2
    sub x17, x16, x17               // imp = isa - imp_offs
.if \Mode == NORMAL
    br  x17
.elseif \Mode == LOOKUP
    orr x16, x16, #3 // for instrumentation, note that we hit a constant cache
    SignAsImp x17
    ret
.else
.abort  unhandled mode \Mode
.endif

5:  ldursw  x9, [x10, #-8]          // offset -8 is the fallback offset
    add x16, x16, x9            // compute the fallback isa
    b   LLookupStart\Function       // lookup again with a new isa
.endif
#endif // CONFIG_USE_PREOPT_CACHES

.endmacro

objc_msgSend(receiver,sel,...)流程

  1. cmp 比较指令,判断消息接收者receiver是否存在
  2. 通过消息接收者receiver的isa找到对应的class
  3. class通过内存平移找到cache
  4. 从cache里面找buckets
  5. 遍历bucket对比sel
  6. 如果buckets有对应的sel,则cacheHit命中cache,返回imp
  7. buckets没有对应的sel,则调用__objc_msgSend_uncached

调用方法的时候底层会objc_msgSend查找方法的imp

发送消息的是一个对象。如果要找类对象需要通过对象的isa指针与mask查找到类对象。

objc_msgSend:发送消息。 isa-->消息的接受者-->类(对象方法)或元类(类方法)

万物皆对象 类也是对象 objc_class继承objc_object

方法有方法缓存

方法缓存存在cache_t中,cache_t存在类对象objc_class结构体中。

在objc_class结构体中根据偏移计算相应结构体指针。

cache_t是一个结构体 里面有一个存储方法缓存的bucket_t数组,bucket_t里面有_imp_sel

根据sel和imp在方法缓存中,如果缓存中找不到(缓存没有命中),则来到__objc_msgSend_uncached方法中进行没有方法缓存的查找,才从methodList中找(遍历方法列表)。

总结

objc_msgSend在查找的时候先进行一个缓存的命中,通过地址的与运算,拿到当前的缓存,和当前要查找的sel进行比对,如果比对上就命中了,直接使用。没有比对上则进行__objc_msgSend_uncached。

__objc_msgSend_uncached

    STATIC_ENTRY __objc_msgSend_uncached
    UNWIND __objc_msgSend_uncached, FrameWithNoSaves

    // THIS IS NOT A CALLABLE C FUNCTION
    // Out-of-band p15 is the class to search

    MethodTableLookup
    TailCallFunctionPointer x17

    END_ENTRY __objc_msgSend_uncached


    STATIC_ENTRY __objc_msgLookup_uncached
    UNWIND __objc_msgLookup_uncached, FrameWithNoSaves

    // THIS IS NOT A CALLABLE C FUNCTION
    // Out-of-band p15 is the class to search

    MethodTableLookup
    ret

    END_ENTRY __objc_msgLookup_uncached

__objc_msgSend_uncached会调用MethodTableLookup

MethodTableLookup调用_lookUpImpOrForward

.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

没有查找到缓存的时候,需要遍历方法列表。lookUpImpOrForward。

IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior){} 从汇编到C++。

方法的慢速查找

lookUpImpOrForward

在快速查找流程中,方法缓存未命中。底层就走到慢速查找流程,并一路从汇编走到lookUpImpOrForward函数。

缓存没有就去找methodList,从类的data_bits中的data数据中找(data数据中有 属性列表 成员变量 方法列表 协议列表)。

lookUpImpOrForward :methodList 自己找不到 找到getSuperClass() 接着循环找。

调用 lookUpImpOrForward 方法,返回值是个 IMP 指针,如果查找到了调用函数的 IMP ,则进行方法的访问

  1. 先当前类对象的方法列表method_t中遍历方法列表。
  2. 沿着当前继承链当中的superClass,指针的指向,
  3. 先找父类的cahe。
  4. 然后再找父类的method_t来进行方法遍历查找。

  5. 一直遍历到Root Class(NSObject)的父类nil。然后消息转发。

  6. 上面只要找到之后,就存到cache中,下次就是快速查找,不用再慢速查找了。

在objc-runtime-new文件中找到lookUpImpOrForward的定义。

//返回IMP
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;
    }

    runtimeLock.lock();

    checkIsKnownClass(cls);

    cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);
    // runtimeLock may have been dropped but is now locked again
    runtimeLock.assertLocked();
    curClass = cls;

    //死循环
    for (unsigned attempts = unreasonableClassCount();;) {
        if (curClass->cache.isConstantOptimizedCache(/* strict */true)) {
          //再一次从cache里面找imp
          //目的:防止多线程操作时,刚好调用函数,此时缓存进来了
#if CONFIG_USE_PREOPT_CACHES
            imp = cache_getImp(curClass, sel);
            if (imp) goto done_unlock;
            curClass = curClass->cache.preoptFallbackClass();
#endif
        } else {
            // curClass method list.
          //从当前类的方法列表中找
            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;
            }
        }

        // Halt if there is a cycle in the superclass chain.
        if (slowpath(--attempts == 0)) {
            _objc_fatal("Memory corruption in class list.");
        }

        // 从父类中找cache
        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;
        }
        if (fastpath(imp)) {
            // Found the method in a superclass. Cache it in this class.
            goto done;
        }
    }

    // No implementation found. Try method resolver once.
        //动态方法解析,只会执行一次。
    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);//里面会调用cache的insert方法
    }
 done_unlock:
    runtimeLock.unlock();
    if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
        return nil;
    }
    return imp;
}

子类调用父类方法,缓存放在父类里,因为curClass变为了父类curClass = curClass->getSuperclass()

二分查找

template<class getNameFunc>
ALWAYS_INLINE static method_t *
findMethodInSortedMethodList(SEL key, const method_list_t *list, const getNameFunc &getName)
{
  //二分查找
    ASSERT(list);

    auto first = list->begin();//auto根据设置的值自动分配类型
    auto base = first;
    decltype(first) probe;

    uintptr_t keyValue = (uintptr_t)key;
    uint32_t count;

    for (count = list->count; count != 0; count >>= 1) {
        probe = base + (count >> 1);//右移一位就是除以2

        uintptr_t probeValue = (uintptr_t)getName(probe);

        if (keyValue == probeValue) {
            // `probe` is a match.
            // Rewind looking for the *first* occurrence of this value.
            // This is required for correct category overrides.
          //为了优先调用分类的方法,分类的方法在类的方法method_t的前面
            while (probe > first && keyValue == (uintptr_t)getName((probe - 1))) {
                probe--;
            }
            return &*probe;
        }

        if (keyValue > probeValue) {
            base = probe + 1;
            count--;
        }
    }

    return nil;
}

先遍历当前类的方法列表,传参是class和sel。

方法列表是一个哈希表。方法列表是一个数组,数组里面的每一个index是经过哈希函数计算得到。index对应一个链表。

遍历列表查找的是method_t结构体,在里面折半查找

分类也是有方法的,分类的同名方法放在前面。

本质上方法列表是一个数组,分类编译进来的时候,分类会把方法数组的前面腾空,分类的方法放在前面。

所以折半查找到的方法可能不是最终要用的方法。所以需要再进行向前查找。直到找到合适的method_t。

方法的本质是method_t

Method_t结构体中有三个成员变量:

  1. SEL name 方法选择器,表示方法名
  2. char *types 表示方法的类型编码Type Encoding,以返回值类型+参数类型的组合编码
  3. IMP imp 表示方法的实现,
  4. 第一个参数:消息的接收者
  5. 第二个参数:方法的
  6. ...代表可选参数,前面的id代表返回值
struct method_t {
    SEL name;           //sel 函数名
    const char *types;  //方法签名:方法编号 返回值 参数.v@:   v是void @是对象 :是SEL
    MethodListIMP imp;  //imp

    struct SortBySELAddress :
        public std::binary_function<const method_t&,
                                    const method_t&, bool>
    {
        bool operator() (const method_t& lhs,
                         const method_t& rhs)
        { return lhs.name < rhs.name; }
    };
};

如果查找到是一个类方法:元类的方法列表中查找。

快速查找和慢速查找都没有找到,则进行动态方法解析

消息转发机制 动态特性

如果没有查到对于方法的 IMP 指针,则进行消息转发机制

_objc_msgForward消息转发做的几件事

1. 动态方法解析

动态添加方法的实现,不是消息转发。

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 (!lookUpImpOrNilTryCache(inst, sel, cls)) {
            resolveInstanceMethod(inst, sel, cls);//还会掉一次实例方法的动态实现
        }
    }

    // chances are that calling the resolver have populated the cache
    // so attempt using it
    return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);
}

resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)

拦截处理。add_method 重新给实例对象(类对象)添加方法

里面有两个方法

resolveInstanceMethod(inst, sel, cls);

resolveClassMethod(inst, sel, cls);

调用 resolveInstanceMethod:resolveClassMethod:,允许用户在此时为该Class动态添加方法实现。如果实现了,则调用并返回YES,那么重新开始objc_msgSend流程。这一次对象会响应这个选择器,一般是因为它已经调用过class_addMethod。

class_addMethod本质上是在方法列表中建立SEL和新的IMP关系,新的IMP是自己做的。如果仍没实现,继续下面的动作。

动态解析的方法,同样会缓存在cache_t。

2. 消息转发流程

快速转发 慢速转发

a. 快速转发

重定向 forward(备用接收者阶段,obj_msgForward 方法的转发)找一个备用的接收者来处理消息。

如果第一层转发返回 NO ,则会进行第二层转发,调用forwardingTargetForSelector:,把调用转发到另一个对象,这是类级别的转发,调用另一个类的相同的方法。

消息缓存在备用接收者里。

实例方法是- (id)forwardingTargetForSelector:(SEL)aSelector

类方法是+ (id)forwardingTargetForSelector:(SEL)aSelector

注意:这里不要返回self,否则会形成死循环。

b. 慢速转发 完整的消息转发

obj_msgForward标准消息转发阶段,消息拦截自己处理。

如果上面转发返回 nil ,则会进入这一层处理

这层会调用 methodSignatureForSelector:尝试获得一个方法签名。如果获取不到则直接调用doesNotRecognizeSelector:抛出异常。如果能获取到,则返回非nil,创建一个NSInvocation并传给forwardInvocation:

调用forwardInvocation:方法,将上面获取到的方法签名包装成Invocation传入,如何处理就在这里面了。

通过方法签名锁定forwardInvocation 谁能处理谁处理

Invocation会绑定 target aseletor.

这是完整的消息转发,因为可以返回方法签名、动态指定调用方法的Target

好处

还可以继续重定向,还可以修改方法主体。操作的可自由度高。

例:aspect 切面

c. 如果转发都失败

调用doesNotRecognizeSelector:,crash抛出异常。


总结

_objc_msgForward在进行消息转发的过程中会涉及以下几个方法:

  1. 动态方法解析:resolveInstanceMethod:(resolveClassMethod:)

class_addMethod返回YES则处理成功

返回NO则往下走

  1. 快速转发:forwardingTargetForSelector:

找一个备用接受者

  1. 慢速转发:methodSignatureForSelector: 和 forwardInvocation:

完整消息转发

  1. doesNotRecognizeSelector:

一旦调用_objc_msgForward将会跳过查找IMP的过程,直接触发消息转发。

forwardInvocation和forwardingTargetForSelector的区别

forwardingTargetForSelector只能转一个

forwardInvocation可以转多个