2018-08-03 Runtime深入学习消息发送和转发机制

Aug 6th, 2018,更新:添加思维导图
Aug 5th, 2018,更新:添加部分参考链接;

基础要求

有一定C/C++,OC,汇编基础,因为主要涉及苹果开源库Runtime和CoreFoundation相关代码的阅读和理解,文章涉及C代码和部分汇编代码的阅读,实践有限。

Runtime初探

有英文基础的,推荐使用官方在线文档Objective-C Runtime,还有官方已经不再Updated的文档,有兴趣可以了解一下 Objective-C Runtime Programming Guide(官方已停止更新的文档),文档中的信息有限,但是对于想要了解一下Runtime的还是很充足的,这篇文章主要关注的是比较深入的问题,主要都是在阅读代码并追踪代码逻辑来分析。

OC是一门面向对象的动态语言,这意味着它不仅需要一个编译器,也需要一个运行时系统来动态得创建类和对象、进行消息传递和转发。对于消息传递和转发,看过大神iOS程序犭袁的《招聘一个靠谱的 iOS》—参考答案(三)的人相信对Runtime消息传递中的obj_msgSendobj_msgForward有所了解,objc_msgSend在官网上还有一些解释,但是如果想要了解更多,还是需要看源码的~objc_msgForward也是,如_class_lookupMethodAndLoadCache3,lookUpImpOrForward等。

因为OC的特性,所以编译时,会把OC的代码转成C/C++代码,感兴趣的可以在终端上,cdmain.m所在到目录后,使用

clang -rewrite-objc main.m

重新编译生成对应的main.cpp文件。

在OC中,方法调用 就是消息传递的过程,比如在main.m中定义

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *p = [[Person alloc] init];
        [p eat];
    }
    return 0;
}

对应的C/C++代码就是

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        Person *p = ((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)p, sel_registerName("eat"));
    }
    return 0;
}

可以看出来,使用的就是id objc_msgSend(id self, SEL op, ...)来向指定的对象发消息,objc_msgSend的作用就是处理发送到指定对象(self)的消息(op)
方法调用 中,OC代码会先被编译成C/C++代码,即使用obj_msgSend传递消息,如果(op)没有对应的实现(IMP)时,objc_msgSend会先尝试 动态解析方法(Dynamically Resolving Methods) ,如果还是没有找到对应的实现,则会返回_obj_msgForward继而进入 消息转发(Forwarding Messages) ,包含备用接收者完整消息转发两种,如果还是没有找到对应的实现,则会进入 出错处理(Error Handling),返回NSObjectdoesNotRecognizeSelector,整个方法调用的流程就是这样,越早处理,代价越小,直到引起崩溃。
整理后,大致的过程是这样,具体的过程在深入部分讲解:

  1. 动态解析方法,重载resolveInstanceMethod:(或resolveClassMethod:)方法,为该 Class 动态添加实现,返回YES,被标记,然后重新开始,对象会返回新的实现以被obj_msgSend使用;如果仍没实现,往下;
  2. 消息转发之备用接收者,重载forwardingTargetForSelector:方法,重载该方法,存在可以接受消息的对象,则返回非 nil 对象。否则返回 nil ,继续往下;(注意,这里不要返回 self ,否则会形成死循环)
  3. 消息转发之消息重定向,重载methodSignatureForSelector:forwardInvocation:方法,Runtime先调用methodSignatureForSelector:将选择器SEL的信息封装到NSInvocation对象中,然后调用forwardInvocation:时作为参数传入,而forwardInvocation:则可将消息发送给任何指定的对象处理。
  4. 出错处理,调用doesNotRecognizeSelector:方法,一般使用的是NSObject,不需要重载

以上的方法,可以在我的GitHub Demo中找到相关演示,都是声明在基类 NSObject

2018-08-03 Runtime深入学习消息发送和转发机制_第1张图片
NSObject类

调用的顺序可以自行验证,如使用(void)instrumentObjcMessageSends(BOOL)打印Runtime日志,篇幅所致,读者可自行百度用法,或参考本文所使用的GitHub代码。

Runtime深入

以上是从了解如何使用消息的传递和转发机制角度来探索Runtime,比较容易理解,Runtime作为OC核心的内容,是很复杂的,实际上还有很多疑问,如,Runtime是如何实现消息的传递和转发的?以下是我参考搜索到的文章,总结出来的,理解不一定够,希望在写下这个以后能有进一步的理解,文末总结附上思维导图。

下载Runtime开源库,通过开源的库我们就可以发现,整个Runtime库是由C语言和汇编语言写的,其中,obj_msgSend就是用汇编语言写的,以下分析时使用的是obj-msg-arm64.s
汇编源码中包含一些注释,其实阅读起来很是很简单的,就是没有称手的工具可以像成熟IDE那样快速浏览,因为使用的是汇编语言,虽然速度上会快很多,但是需要适应不同平台不同的代码,其中obj-msg-arm64.s是arm64上的,关键代码如下:

1.进入汇编,开始调用_objc_msgSend方法

ENTRY _objc_msgSend
    UNWIND _objc_msgSend, NoFrame
    MESSENGER_START

    cmp x0, #0          // nil check and tagged pointer check
    b.le    LNilOrTagged        //  (MSB tagged pointer looks negative)
    ldr x13, [x0]       // x13 = isa
    and x16, x13, #ISA_MASK // x16 = class  
LGetIsaDone:
    CacheLookup NORMAL      // calls imp or objc_msgSend_uncached

LNilOrTagged:
    b.eq    LReturnZero     // nil check

    // tagged
    mov x10, #0xf000000000000000
    cmp x0, x10
    b.hs    LExtTag
    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]
    b   LGetIsaDone

LExtTag:
    // 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
    
LReturnZero:
    // x0 is already zero
    mov x1, #0
    movi    d0, #0
    movi    d1, #0
    movi    d2, #0
    movi    d3, #0
    MESSENGER_END_NIL
    ret

    END_ENTRY _objc_msgSend

2.通过宏CacheLookup进入缓存寻找SEL

_objc_msgSend调用了宏CacheLooup,其中,CacheLooup是再缓存中寻找对应selector的实现IMP,如果在缓存中找到了IMP,则调用IMP,如果没有找到,则会跳转到__objc_msgSend_uncached
此汇编文件中也包含了CacheLooup的代码:

/********************************************************************
 *
 * CacheLookup NORMAL|GETIMP|LOOKUP
 * 
 * Locate the implementation for a selector in a class method cache.
 *
 * 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

.macro CacheHit
.if $0 == NORMAL
    MESSENGER_END_FAST
    br  x17         // call imp
.elseif $0 == GETIMP
    mov x0, x17         // return imp
    ret
.elseif $0 == LOOKUP
    ret             // return imp via x17
.else
.abort oops
.endif
.endmacro

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

.macro JumpMiss
.if $0 == GETIMP
    b   LGetImpMiss
.elseif $0 == NORMAL
    b   __objc_msgSend_uncached
.elseif $0 == LOOKUP
    b   __objc_msgLookup_uncached
.else
.abort oops
.endif
.endmacro

.macro CacheLookup
    // x1 = SEL, x16 = isa
    ldp x10, x11, [x16, #CACHE] // x10 = buckets, x11 = occupied|mask
    and w12, w1, w11        // x12 = _cmd & mask
    add x12, x10, x12, LSL #4   // x12 = buckets + ((_cmd & mask)<<4)

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

3:  // wrap: x12 = first bucket, w11 = mask
    add x12, x12, w11, UXTW #4  // x12 = buckets+(mask<<4)

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

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

3:  // double wrap
    JumpMiss $0
    
.endmacro

3.缓存中没有找到,进入__objc_msgSend_uncached方法

然后就可以顺藤摸瓜找到__objc_msgSend_uncached,这个方法是在未缓存的方法列表中寻找SEL,找到后就掉用,由源码可知方法中又跳到了宏MethodTableLookup

STATIC_ENTRY __objc_msgSend_uncached
    UNWIND __objc_msgSend_uncached, FrameWithNoSaves

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

    END_ENTRY __objc_msgSend_uncached

MethodTableLookup是在方法列表中寻找,调用了方法__class_lookupMethodAndLoadCache3,寻找IMP,并把IMP添加到缓存

.macro MethodTableLookup
    
    // push frame
    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)]

    // receiver and selector already in x0 and x1
    mov x2, x16
    bl  __class_lookupMethodAndLoadCache3

    // 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

.endmacro

4.调用__class_lookupMethodAndLoadCache3进入方法列表中寻找

__class_lookupMethodAndLoadCache3没有被放在汇编文件中,而是在开源的文件objc-runtime-new.mm中。
__class_lookupMethodAndLoadCache3调用了lookUpImpOrForward(),其作用时寻找IMP并缓存起来,如果没有找到的话,则会进入_class_resolveMethod()尝试寻找动态解析方法,并且因为lookUpImpOrForward()的参数之一resolver为YES,所以,肯定都是允许动态解析的

/***********************************************************************
* _class_lookupMethodAndLoadCache.
* Method lookup for dispatchers ONLY. OTHER CODE SHOULD USE lookUpImp().
* This lookup avoids optimistic cache scan because the dispatcher 
* already tried that.
**********************************************************************/
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
    return lookUpImpOrForward(cls, sel, obj, 
                              YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}
/***********************************************************************
* lookUpImpOrForward.
* The standard IMP lookup. 
* initialize==NO tries to avoid +initialize (but sometimes fails)
* cache==NO skips optimistic unlocked lookup (but uses cache elsewhere)
* Most callers should use initialize==YES and cache==YES.
* 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 lookUpImpOrNil() instead.
**********************************************************************/
IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                       bool initialize, bool cache, bool resolver)
{
    IMP imp = nil;
    bool triedResolver = NO;

    runtimeLock.assertUnlocked();

    // Optimistic cache lookup
    if (cache) {
        imp = cache_getImp(cls, sel);
        if (imp) return imp;
    }

    // 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.read();

    if (!cls->isRealized()) {
        // Drop the read-lock and acquire the write-lock.
        // realizeClass() checks isRealized() again to prevent
        // a race while the lock is down.
        runtimeLock.unlockRead();
        runtimeLock.write();

        realizeClass(cls);

        runtimeLock.unlockWrite();
        runtimeLock.read();
    }

    if (initialize  &&  !cls->isInitialized()) {
        runtimeLock.unlockRead();
        _class_initialize (_class_getNonMetaClass(cls, inst));
        runtimeLock.read();
        // 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
    }

    
 retry:    
    runtimeLock.assertReading();

    // Try this class's cache.

    imp = cache_getImp(cls, sel);
    if (imp) goto done;

    // Try this class's method lists.
    {
        Method meth = getMethodNoSuper_nolock(cls, sel);
        if (meth) {
            log_and_fill_cache(cls, meth->imp, sel, inst, cls);
            imp = meth->imp;
            goto done;
        }
    }

    // Try superclass caches and method lists.
    {
        unsigned attempts = unreasonableClassCount();
        for (Class curClass = cls->superclass;
             curClass != nil;
             curClass = curClass->superclass)
        {
            // Halt if there is a cycle in the superclass chain.
            if (--attempts == 0) {
                _objc_fatal("Memory corruption in class list.");
            }
            
            // Superclass cache.
            imp = cache_getImp(curClass, sel);
            if (imp) {
                if (imp != (IMP)_objc_msgForward_impcache) {
                    // Found the method in a superclass. Cache it in this class.
                    log_and_fill_cache(cls, imp, sel, inst, curClass);
                    goto done;
                }
                else {
                    // Found a forward:: entry in a superclass.
                    // Stop searching, but don't cache yet; call method 
                    // resolver for this class first.
                    break;
                }
            }
            
            // Superclass method list.
            Method meth = getMethodNoSuper_nolock(curClass, sel);
            if (meth) {
                log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
                imp = meth->imp;
                goto done;
            }
        }
    }

    // No implementation found. Try method resolver once.

    if (resolver  &&  !triedResolver) {
        runtimeLock.unlockRead();
        _class_resolveMethod(cls, sel, inst);
        runtimeLock.read();
        // Don't cache the result; we don't hold the lock so it may have 
        // changed already. Re-do the search from scratch instead.
        triedResolver = YES;
        goto retry;
    }

    // No implementation found, and method resolver didn't help. 
    // Use forwarding.

    imp = (IMP)_objc_msgForward_impcache;
    cache_fill(cls, sel, imp, inst);

 done:
    runtimeLock.unlockRead();

    return imp;
}

5.调用_class_resolveMethod()方法尝试动态解析

_class_resolveMethod()的源码在objc-class.mm中

/***********************************************************************
* _class_resolveClassMethod
* Call +resolveClassMethod, looking for a method to be added to class cls.
* cls should be a metaclass.
* Does not check if the method already exists.
**********************************************************************/
static void _class_resolveClassMethod(Class cls, SEL sel, id inst)
{
    assert(cls->isMetaClass());

    if (! lookUpImpOrNil(cls, SEL_resolveClassMethod, inst, 
                         NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
    {
        // Resolver not implemented.
        return;
    }

    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(_class_getNonMetaClass(cls, inst), 
                        SEL_resolveClassMethod, sel);

    // Cache the result (good or bad) so the resolver doesn't fire next time.
    // +resolveClassMethod adds to self->ISA() a.k.a. cls
    IMP imp = lookUpImpOrNil(cls, sel, inst, 
                             NO/*initialize*/, YES/*cache*/, NO/*resolver*/);

    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 resolveClassMethod:%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));
        }
    }
}


/***********************************************************************
* _class_resolveInstanceMethod
* Call +resolveInstanceMethod, looking for a method to be added to class cls.
* cls may be a metaclass or a non-meta class.
* Does not check if the method already exists.
**********************************************************************/
static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst)
{
    if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls, 
                         NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
    {
        // Resolver not implemented.
        return;
    }

    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(cls, SEL_resolveInstanceMethod, sel);

    // Cache the result (good or bad) so the resolver doesn't fire next time.
    // +resolveInstanceMethod adds to self a.k.a. cls
    IMP imp = lookUpImpOrNil(cls, sel, inst, 
                             NO/*initialize*/, YES/*cache*/, NO/*resolver*/);

    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));
        }
    }
}
/***********************************************************************
* _class_resolveMethod
* Call +resolveClassMethod or +resolveInstanceMethod.
* Returns nothing; any result would be potentially out-of-date already.
* Does not check if the method already exists.
**********************************************************************/
void _class_resolveMethod(Class cls, SEL sel, id inst)
{
    if (! cls->isMetaClass()) {
        // try [cls resolveInstanceMethod:sel]
        _class_resolveInstanceMethod(cls, sel, inst);
    } 
    else {
        // try [nonMetaClass resolveClassMethod:sel]
        // and [cls resolveInstanceMethod:sel]
        _class_resolveClassMethod(cls, sel, inst);
        if (!lookUpImpOrNil(cls, sel, inst, 
                            NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
        {
            _class_resolveInstanceMethod(cls, sel, inst);
        }
    }
}

如上面Runtime初探可知,如果对象实现了resolveInstanceMethod(或resolveClassMethod)则resolved为YES,对应的IMP也会被缓存起来。由步骤4的lookUpImpOrForward()可知,动态解析方法后,不管成功与否,都会进行一次重试,但也仅仅一次,如果实现了相应的动态解析方法,则因为缓存中已经被加入了新的IMP,所以会顺利执行IMP,从而结束整个objc_msgSend;如果未重载实现动态解析方法,则在因为缓存中没有IMP而且因为lookUpImpOrForward()中的triedResolver已经被置为YES,所以会返回一个默认的IMP_objc_msgForward_impcache,并缓存之。

6.进入转发

因为lookUPImpOrForward()返回的是一个_objc_msgForward_impcache,在汇编文件__objc_msgSend_uncached的宏MethodTableLookup执行完以后会被调用,执行消息转发机制

/********************************************************************
*
* id _objc_msgForward(id self, SEL _cmd,...);
*
* _objc_msgForward is the externally-callable
*   function returned by things like method_getImplementation().
* _objc_msgForward_impcache is the function pointer actually stored in
*   method caches.
*
********************************************************************/

    STATIC_ENTRY __objc_msgForward_impcache

    MESSENGER_START
    nop
    MESSENGER_END_SLOW

    // No stret specialization.
    b   __objc_msgForward

    END_ENTRY __objc_msgForward_impcache

    
    ENTRY __objc_msgForward

    adrp    x17, __objc_forward_handler@PAGE
    ldr x17, [x17, __objc_forward_handler@PAGEOFF]
    br  x17
    
    END_ENTRY __objc_msgForward

7.消息转发机制,objc_setForwardHandler设置__objc_forward_handler

以下部分没有找到对应的源码信息,都是从网络上整理过来的,

objc_setForwardHandler的调用不是在Runtime中,而是在 CoreFoundation 源文件CFRuntime.c中的__CFInitialize(),不过苹果公布的源码中没有包含相关代码的调用,网络上大神: Hmmm, What's that Selector?中文可以参考这个,如图:

反编译后的 __CFInitialize() 汇编代码

红色标出的那三个指令就是把 __CF_forwarding_prep_0___forwarding_prep_1___ 作为参数调用 objc_setForwardHandler 方法。
再由下面这个崩溃时堆栈抛出的信息来看,可以看出 _CF_forwarding_prep_0 函数调用了 ___forwarding___ 函数,接着又调用了 doesNotRecognizeSelector 方法,最后抛出异常.
2018-08-03 Runtime深入学习消息发送和转发机制_第2张图片
崩溃时抛出的堆栈信息

而消息转发的逻辑几乎全在 __forwarding__中:

void __forwarding__(BOOL isStret, void *frameStackPointer, ...) {
  id receiver = *(id *)frameStackPointer;
  SEL sel = *(SEL *)(frameStackPointer + 4);

  Class receiverClass = object_getClass(receiver);

  if (class_respondsToSelector(receiverClass, @selector(forwardingTargetForSelector:))) {
    id forwardingTarget = [receiver forwardingTargetForSelector:sel];
    if (forwardingTarget) {
      return objc_msgSend(forwardingTarget, sel, ...);
    }
  }

  const char *className = class_getName(object_getClass(receiver));
  const char *zombiePrefix = "_NSZombie_";
  size_t prefixLen = strlen(zombiePrefix);
  if (strncmp(className, zombiePrefix, prefixLen) == 0) {
    CFLog(kCFLogLevelError,
          @"-[%s %s]: message sent to deallocated instance %p",
          className + prefixLen,
          sel_getName(sel),
          receiver);
    
  }

  if (class_respondsToSelector(receiverClass, @selector(methodSignatureForSelector:))) {
    NSMethodSignature *methodSignature = [receiver methodSignatureForSelector:sel];
    if (methodSignature) {
      BOOL signatureIsStret = [methodSignature _frameDescriptor]->returnArgInfo.flags.isStruct;
      if (signatureIsStret != isStret) {
        CFLog(kCFLogLevelWarning ,
              @"*** NSForwarding: warning: method signature and compiler disagree on struct-return-edness of '%s'.  Signature thinks it does%s return a struct, and compiler thinks it does%s.",
              sel_getName(sel),
              signatureIsStret ? "" : not,
              isStret ? "" : not);
      }
      if (class_respondsToSelector(receiverClass, @selector(forwardInvocation:))) {
        NSInvocation *invocation = [NSInvocation _invocationWithMethodSignature:methodSignature
                                                                          frame:frameStackPointer];
        [receiver forwardInvocation:invocation];

        void *returnValue = NULL;
        [invocation getReturnValue:&value];
        return returnValue;
      } else {
        CFLog(kCFLogLevelWarning ,
              @"*** NSForwarding: warning: object %p of class '%s' does not implement forwardInvocation: -- dropping message",
              receiver,
              className);
        return 0;
      }
    }
  }

  const char *selName = sel_getName(sel);
  SEL *registeredSel = sel_getUid(selName);

  if (sel != registeredSel) {
    CFLog(kCFLogLevelWarning ,
          @"*** NSForwarding: warning: selector (%p) for message '%s' does not match selector known to Objective C runtime (%p)-- abort",
          sel,
          selName,
          registeredSel);
  } else if (class_respondsToSelector(receiverClass, @selector(doesNotRecognizeSelector:))) {
    [receiver doesNotRecognizeSelector:sel];
  } else {
    CFLog(kCFLogLevelWarning ,
          @"*** NSForwarding: warning: object %p of class '%s' does not implement doesNotRecognizeSelector: -- abort",
          receiver,
          className);
  }

  // [The point of no return](http://www.youtube.com/watch?v=AyyCz9z1s_M).
  kill(getpid(), 9);
}

基本逻辑就是按照上面Runtime初探中那样运行的。
实际测试中,在我的Demo里,如果我实现了所有上述5个方法后,即+(BOOL)resolveInstanceMethod:(SEL)sel+(BOOL)resolveClassMethod:(SEL)sel-(id)forwardingTargetForSelector:(SEL)aSelector-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector-(void)forwardInvocation:(NSInvocation *)anInvocation,并故意让5个方法都被执行到,最后再执行doesNotRecognizeSelector:,理想中应该是每个方法执行一次,但是实际情况是resolveInstanceMethod:会被执行了两次,而且涉及到一个方法_forwardStackInvocation:,输出如下:

2018-08-05 21:29:49.317390+0800 MCRnRExploration[11739:1817234] Eating
2018-08-05 21:29:49.318847+0800 MCRnRExploration[11739:1817234] +[Person resolveInstanceMethod:] recv run, order:1
2018-08-05 21:29:49.319489+0800 MCRnRExploration[11739:1817234] -[Person forwardingTargetForSelector:] recv run, order:2
2018-08-05 21:29:49.320052+0800 MCRnRExploration[11739:1817234] -[Person methodSignatureForSelector:] recv run, order:3
2018-08-05 21:29:49.320643+0800 MCRnRExploration[11739:1817234] +[Person resolveInstanceMethod:] recv _forwardStackInvocation:, order:4
2018-08-05 21:29:49.321243+0800 MCRnRExploration[11739:1817234] -[Person forwardInvocation:] recv run, order:5
2018-08-05 21:29:49.321742+0800 MCRnRExploration[11739:1817234] -[Person run]: unrecognized selector sent to instance 0x1004869f0
2018-08-05 21:29:49.324693+0800 MCRnRExploration[11739:1817234] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Person run]: unrecognized selector sent to instance 0x1004869f0'
*** First throw call stack:
(
    0   CoreFoundation                      0x00007fff48d7a2fb __exceptionPreprocess + 171
    1   libobjc.A.dylib                     0x00007fff6f6ebc76 objc_exception_throw + 48
    2   CoreFoundation                      0x00007fff48e12da4 -[NSObject(NSObject) doesNotRecognizeSelector:] + 132
    3   MCRnRExploration                    0x0000000100001bab -[Person forwardInvocation:] + 251
    4   CoreFoundation                      0x00007fff48cf03ac ___forwarding___ + 748
    5   CoreFoundation                      0x00007fff48cf0038 _CF_forwarding_prep_0 + 120
    6   MCRnRExploration                    0x0000000100001c72 main + 130
    7   libdyld.dylib                       0x00007fff702da145 start + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException

调查后发现,在Foundation.framework的NSXPSDistantObject类中,iOS11.1中发生了变化,新增了一个方法-(void)_forwardStackInvocation:(id)arg1,而在之前的iOS10.2中就没有,这个问题暂时就搁着了,有高手了解的话,可以评论里分享一下,谢谢了!!!

总结

总结成思维导图如下(图片较大,推荐使用查看原图)


2018-08-03 Runtime深入学习消息发送和转发机制_第3张图片
Runtime消息传递与转发机制

相关文章链接:

GitHub消息传递及消息转发Demo
iOS11.1 NSXPSDistantObject
iOS10.2 NSXPSDistantObject
iOS 常见知识点(一):Runtime
objc_setForwardHandler的注册和实现参考
▼▼▼
Hmmm, What's that Selector?
Objective-C 消息发送与转发机制原理
▲▲▲
以上两篇需要对苹果开源库Runtime和CoreFoundation有一定了解,至少也要求下载这两个库,着重在CoreFoundation上,因为objc_setForwardHandler是在CoreFoundation的CFRuntime中被使用的

相关资料下载路径:

Runtime开源库(压缩包下载)
Runtime源码库(在线浏览)
CoreFoundation开源库(压缩包下载)
CoreFoundation开源库(在线浏览)
GitHub上Runtime源码库
Objective-C Runtime
Objective-C Runtime Programming Guide(官方已经停止更新,推荐上面那个)

你可能感兴趣的:(2018-08-03 Runtime深入学习消息发送和转发机制)