Runtime-消息发送与转发

简介

OC中方法调用就是向指定对象发送消息.

OC是一门动态语言,OC中所有方法的调用以及类的生成都是通过Runtime在运行时进行,我们可以通过类名/方法名反射得到相应的类和方法,也可以替换某个类的方法为新的实现.

消息发送和转发流程可以概括为:消息发送(Messaging)是 Runtime 通过 selector 快速查找 IMP 的过程,有了函数指针就可以执行对应的方法实现;消息转发(Message Forwarding)是在查找 IMP 失败后执行一系列转发流程的慢速通道,如果不作转发处理,则会打日志和抛出异常。

消息发送阶段解析

[receiver message],这是一个方法调用的格式,clang一下其转化代码:

//OC代码
[self test];

//clang之后
((void (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("test"));

//objc_msgSend
/** 
 * 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);

那么objc_msgSend方法会做些什么工作,在这篇文章中,作者给出一下结论 :

  1. Check for ignored selectors (GC) and short-circuit.
  2. Check for nil target.
    1. If nil & nil receiver handler configured, jump to handler
    2. If nil & no handler (default), cleanup and return.
  3. Find the IMP on the class of the target
    1. If found, jump to it.
    2. Not found, Perform step 4
  4. Search the class’s method cache for the method IMP
    1. If found, jump to it.
    2. Not found: lookup the method IMP in the class itself

上面流程结束后:

  • If found, jump to it.
  • If not found, jump to forwarding mechanism.

总结上面的流程 :

  1. 检测是否忽略 selectors
  2. 检测target是否为空
    如果为空,并且有响应的nil处理函数,就跳转到相应的处理中.如果没有nil处理函数,就会自动清理现场并返回,这一点就是为何在OC中给nil发送消息不会崩溃的原因.
  3. 如果target不为空,在该class的缓存中查找方法对应的IMP实现.
    如果找到,就跳转执行
    如果没找到,就在方法分发表里面继续查找,一直到NSObject为止,查找过程如下图:
方法分发表查找过程
  1. 如果最后还没有找到,那就进入到消息转发阶段.到这里,消息发送阶段的工作完成,这一阶段主要完成的是通过select()快速查找IMP的过程。

objc_msgSend源码解析

先来看一下objc_msgSend的伪代码,

//下面方法的逻辑就是获取imp指针并调用方法
id objc_msgSend(id self, SEL _cmd, ...) {
  Class class = object_getClass(self);
  IMP imp = class_getMethodImplementation(class, _cmd);
  return imp ? imp(self, _cmd, ...) : 0;
}

在runtime源码中,objc_msgSend的代码是用汇编语言编写的,针对不同架构有不同实现,下面给出一份x86_64架构下的源码,对应代码可以在objc-msg-x86_64.s中找到

.data
    .align 3
    .globl _objc_debug_taggedpointer_classes
_objc_debug_taggedpointer_classes:
    .fill 16, 8, 0
    .globl _objc_debug_taggedpointer_ext_classes
_objc_debug_taggedpointer_ext_classes:
    .fill 256, 8, 0

    ENTRY _objc_msgSend
    UNWIND _objc_msgSend, NoFrame

    NilTest NORMAL

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

    NilTestReturnZero NORMAL

    GetIsaSupport NORMAL

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

    END_ENTRY _objc_msgSend

    
    ENTRY _objc_msgLookup

    NilTest NORMAL    //判断消息接受者是否为空, 为空则返回

    GetIsaFast NORMAL       // r10 = self->isa
    CacheLookup NORMAL, LOOKUP  // returns IMP on success

    NilTestReturnIMP NORMAL

    GetIsaSupport NORMAL
    
    --------------------------------------------------------------------
    //__objc_msgSend_uncached的源码
    STATIC_ENTRY __objc_msgSend_uncached
    UNWIND __objc_msgSend_uncached, FrameWithNoSaves
    
    // THIS IS NOT A CALLABLE C FUNCTION
    // Out-of-band r10 is the searched class

    // r10 is already the class to search
    MethodTableLookup NORMAL    // r11 = IMP
    jmp *%r11           // goto *imp

    END_ENTRY __objc_msgSend_uncached

LCacheMiss为分割线,_objc_msgSend的步骤分为两步: CacheLookupMethodTableLookup.

下面仔细分析其中的一些过程 :

//NilTest   NORMAL

/////////////////////////////////////////////////////////////////////
//
// NilTest return-type
//
// Takes:   $0 = NORMAL or FPRET or FP2RET or STRET
//      %a1 or %a2 (STRET) = receiver
//
// On exit:     Loads non-nil receiver in %a1 or %a2 (STRET)
//      or returns.
//
/////////////////////////////////////////////////////////////////////
.macro NilTest
.if $0 != STRET
    testq   %a1, %a1    //testq : 逻辑与操作,判断%a1是否为空
.else
    testq   %a2, %a2
.endif
    PN
    jz  LNilTestSlow_f  //如果为0(空),则跳转LNilTestSlow_f,这个应该就是一个空处理,具体过程是
                        // NilTest  NORMAL -为空-> LNilTestSlow_f --> ZeroReturn
.endmacro


//------ ZeroReturn ------
.macro ZeroReturn
    xorl    %eax, %eax   //按位异或,相同的位置为0,不同的位置为1,eax和eax的每一位都相同,所以相当于清零
    xorl    %edx, %edx  //将寄存器 %edx 设置为 0
    xorps   %xmm0, %xmm0  //应该也是清空寄存器
    xorps   %xmm1, %xmm1  //应该也是清空寄存器
.endmacro


%a1 or %a2 (STRET) = receiver,所以NilTest是判断消息接受者是否为空,为空的话跳转到空处理ZeroReturn,因为这个地方对消息接受者为nil的情况做了判断,所以在OC中对nil发送消息是不会崩溃的.

GetIsaFast宏可以快速地获取到对象的 isa 指针地址(放到 r11 寄存器,r10会被重写;在 arm 架构上是直接赋值到 r9)

//GetIsaFast NORMAL

/////////////////////////////////////////////////////////////////////
//
// GetIsaFast return-type
// GetIsaSupport return-type
//
// Sets r10 = obj->isa. Consults the tagged isa table if necessary.
//
// Takes:   $0 = NORMAL or FPRET or FP2RET or STRET
//      a1 or a2 (STRET) = receiver
//
// On exit:     r10 = receiver->isa
//      r11 is clobbered
//
/////////////////////////////////////////////////////////////////////

.macro GetIsaFast
.if $0 != STRET
    testb   $$1, %a1b
    PN
    jnz LGetIsaSlow_f
    movq    $$ ISA_MASK, %r10
    andq    (%a1), %r10
.else
    testb   $$1, %a2b
    PN
    jnz LGetIsaSlow_f
    movq    $$ ISA_MASK, %r10
    andq    (%a2), %r10
.endif
LGetIsaDone:    
.endmacro

CacheLookup: 在方法缓存列表中寻找IMP指针

/////////////////////////////////////////////////////////////////////
//
// CacheLookup  return-type, caller
//
// Locate the implementation for a class in a selector's method cache.
//
// Takes: 
//    $0 = NORMAL, FPRET, FP2RET, STRET
//    $1 = CALL, LOOKUP, GETIMP
//    a1 or a2 (STRET) = receiver
//    a2 or a3 (STRET) = selector
//    r10 = class to search
//
// On exit: r10 clobbered
//      (found) calls or returns IMP in r11, eq/ne set for forwarding
//      (not found) jumps to LCacheMiss, class still in r10
//
/////////////////////////////////////////////////////////////////////
.macro  CacheLookup
.if $0 != STRET
    movq    %a2, %r11       // r11 = _cmd
.else
    movq    %a3, %r11       // r11 = _cmd
.endif
    andl    24(%r10), %r11d     // r11 = _cmd & class->cache.mask
    shlq    $$4, %r11       // r11 = offset = (_cmd & mask)<<4
    addq    16(%r10), %r11      // r11 = class->cache.buckets + offset

.if $0 != STRET
    cmpq    cached_sel(%r11), %a2   // if (bucket->sel != _cmd)
.else
    cmpq    cached_sel(%r11), %a3   // if (bucket->sel != _cmd)
.endif
    jne     1f          //     scan more
    // CacheHit must always be preceded by a not-taken `jne` instruction
    CacheHit $0, $1         // call or return imp

1:
    // loop
    cmpq    $$1, cached_sel(%r11)
    jbe 3f          // if (bucket->sel <= 1) wrap or miss

    addq    $$16, %r11      // bucket++
2:  
.if $0 != STRET
    cmpq    cached_sel(%r11), %a2   // if (bucket->sel != _cmd)
.else
    cmpq    cached_sel(%r11), %a3   // if (bucket->sel != _cmd)
.endif
    jne     1b          //     scan more
    // CacheHit must always be preceded by a not-taken `jne` instruction
    CacheHit $0, $1         // call or return imp

3:
    // wrap or miss
    jb  LCacheMiss_f        // if (bucket->sel < 1) cache miss
    // wrap
    movq    cached_imp(%r11), %r11  // bucket->imp is really first bucket
    jmp     2f

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

1:
    // loop
    cmpq    $$1, cached_sel(%r11)
    jbe 3f          // if (bucket->sel <= 1) wrap or miss

    addq    $$16, %r11      // bucket++
2:  
.if $0 != STRET
    cmpq    cached_sel(%r11), %a2   // if (bucket->sel != _cmd)
.else
    cmpq    cached_sel(%r11), %a3   // if (bucket->sel != _cmd)
.endif
    jne     1b          //     scan more
    // CacheHit must always be preceded by a not-taken `jne` instruction
    CacheHit $0, $1         // call or return imp         找到imp,就调用/返回

3:
    // double wrap or miss
    jmp LCacheMiss_f

.endmacro

r10中存储类,IMP存储在r11中(不同版本的runtime,存储位置不同),返回时有两种情况 :

  1. 找到IMP,就调用/返回r11中的IMP
  2. 未找到,就跳转到LCacheMiss_f,进行下一阶段

如果程序跳到LCacheMiss,就说明未命中缓存。这个时候就要开始下一阶段MethodTableLookup的查找了。

// ------ __objc_msgSend_uncached ------
STATIC_ENTRY __objc_msgSend_uncached
UNWIND __objc_msgSend_uncached, FrameWithNoSaves
    
// THIS IS NOT A CALLABLE C FUNCTION
// Out-of-band r10 is the searched class

// r10 is already the class to search
MethodTableLookup NORMAL    // r11 = IMP
jmp *%r11           // goto *imp

END_ENTRY __objc_msgSend_uncached
    
//------ MethodTableLookup ------
/////////////////////////////////////////////////////////////////////
//
// MethodTableLookup NORMAL|STRET
//
// Takes:   a1 or a2 (STRET) = receiver
//      a2 or a3 (STRET) = selector to search for
//      r10 = class to search
//
// On exit: imp in %r11, eq/ne set for forwarding
//
/////////////////////////////////////////////////////////////////////
.macro MethodTableLookup

    push    %rbp
    mov %rsp, %rbp
    
    sub $$0x80+8, %rsp      // +8 for alignment

    movdqa  %xmm0, -0x80(%rbp)
    push    %rax            // might be xmm parameter count
    movdqa  %xmm1, -0x70(%rbp)
    push    %a1
    movdqa  %xmm2, -0x60(%rbp)
    push    %a2
    movdqa  %xmm3, -0x50(%rbp)
    push    %a3
    movdqa  %xmm4, -0x40(%rbp)
    push    %a4
    movdqa  %xmm5, -0x30(%rbp)
    push    %a5
    movdqa  %xmm6, -0x20(%rbp)
    push    %a6
    movdqa  %xmm7, -0x10(%rbp)

    // _class_lookupMethodAndLoadCache3(receiver, selector, class)

.if $0 == NORMAL
    // receiver already in a1
    // selector already in a2
.else
    movq    %a2, %a1
    movq    %a3, %a2
.endif
    movq    %r10, %a3
    
    //上面是准备__class_lookupMethodAndLoadCache3的参数, a1->receiver, a2->selector, r10->class
    call    __class_lookupMethodAndLoadCache3

    // IMP is now in %rax
    movq    %rax, %r11 //IMP会重r11放到rax中返回

    movdqa  -0x80(%rbp), %xmm0
    pop %a6
    movdqa  -0x70(%rbp), %xmm1
    pop %a5
    movdqa  -0x60(%rbp), %xmm2
    pop %a4
    movdqa  -0x50(%rbp), %xmm3
    pop %a3
    movdqa  -0x40(%rbp), %xmm4
    pop %a2
    movdqa  -0x30(%rbp), %xmm5
    pop %a1
    movdqa  -0x20(%rbp), %xmm6
    pop %rax
    movdqa  -0x10(%rbp), %xmm7

.if $0 == NORMAL
    cmp %r11, %r11      // set eq for nonstret forwarding
.else
    test    %r11, %r11      // set ne for stret forwarding
.endif
    
    leave

.endmacro

下面来看下__class_lookupMethodAndLoadCache3方法,在Runtime源码中(objc-runtime-new.mm)可以找到该方法实现:

/***********************************************************************
* _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. //标准的IMP查找
* 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;

    //对debug模式下的assert进行unlock
    runtimeLock.assertUnlocked();

    // Optimistic cache lookup
    if (cache) {
        //如果cache参数为YES,会调用cache_getImp方法从缓存中查找IMP
        //_class_lookupMethodAndLoadCache3 中cache参数为NO,所以这一步不会执行
        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.lock();
    checkIsKnownClass(cls);

    if (!cls->isRealized()) {
        realizeClass(cls);
    }
    
    //如果initialize参数为YES,并且是第一次用到这个类
    if (initialize  &&  !cls->isInitialized()) {
        runtimeLock.unlock(); //对runtimeLock加写操作锁
        _class_initialize (_class_getNonMetaClass(cls, inst));//初始化类,申请空间
        runtimeLock.lock(); //解锁
        // 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.assertLocked();

    // Try this class's cache.
    //在该类的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.
    //在父类的缓存和方法列表中查找,循环直到superclass为nil,也就是到NSObject
    {
        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.
    // 如果resolver参数为YES,并且没有尝试过方法解析
    // 其实就是调用_class_resolveMethod方法,看看开发者有没有动态增加方法实现
    if (resolver  &&  !triedResolver) {
        runtimeLock.unlock();
        _class_resolveMethod(cls, sel, inst);
        runtimeLock.lock();
        // 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,并且加入了缓存中,这个是一个标志,如果在查找过程中,IMP==_objc_msgForward_impcache,则会返回nil,否则返回imp,后面的lookUpImpOrNil方法中可以看到
    imp = (IMP)_objc_msgForward_impcache;
    cache_fill(cls, sel, imp, inst);

 done:
    runtimeLock.unlock();

    return imp;
}

在来看下查找方法动态实现的过程 :

/***********************************************************************
* _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()) {
    //如果cls不是元类,则查找实例方法
        // 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*/)) 
        {
        //如果lookUpImpOrNil返回nil,就代表在父类中的缓存中找到,于是需要再调用一次_class_resolveInstanceMethod方法。保证给sel添加上了对应的IMP。
            _class_resolveInstanceMethod(cls, sel, inst);
        }
    }
}

/***********************************************************************
* lookUpImpOrNil.
* Like lookUpImpOrForward, but returns nil instead of _objc_msgForward_impcache
**********************************************************************/
IMP lookUpImpOrNil(Class cls, SEL sel, id inst, 
                   bool initialize, bool cache, bool resolver)
{
    IMP imp = lookUpImpOrForward(cls, sel, inst, initialize, cache, resolver);
    if (imp == _objc_msgForward_impcache) return nil;
    else return imp;
}


消息转发阶段(Message Forwarding)

消息转发阶段,会调用id _objc_msgForward(id self, SEL _cmd,...)方法。在objc-msg-x86_64.s中有其汇编的实现。

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

    STATIC_ENTRY __objc_msgForward_impcache
    // Method cache version

    // THIS IS NOT A CALLABLE C FUNCTION
    // Out-of-band condition register is NE for stret, EQ otherwise.
    
    //jne 不等于则跳转
    jne __objc_msgForward_stret //不等于__objc_msgForward_impcache,则执行__objc_msgForward_stret
    jmp __objc_msgForward       //否则执行__objc_msgForward

    END_ENTRY __objc_msgForward_impcache
    
    
    ENTRY __objc_msgForward
    // Non-stret version
  //消息转发回调,在objc-runtime.mm中可以找到其实现,具体实现看下面内容
    movq    __objc_forward_handler(%rip), %r11
    jmp *%r11

    END_ENTRY __objc_msgForward


    ENTRY __objc_msgForward_stret
    // Struct-return version

    movq    __objc_forward_stret_handler(%rip), %r11
    jmp *%r11

    END_ENTRY __objc_msgForward_stret


    ENTRY _objc_msgSend_debug
    jmp _objc_msgSend
    END_ENTRY _objc_msgSend_debug

    ENTRY _objc_msgSendSuper2_debug
    jmp _objc_msgSendSuper2
    END_ENTRY _objc_msgSendSuper2_debug

    ENTRY _objc_msgSend_stret_debug
    jmp _objc_msgSend_stret
    END_ENTRY _objc_msgSend_stret_debug

    ENTRY _objc_msgSendSuper2_stret_debug
    jmp _objc_msgSendSuper2_stret
    END_ENTRY _objc_msgSendSuper2_stret_debug

    ENTRY _objc_msgSend_fpret_debug
    jmp _objc_msgSend_fpret
    END_ENTRY _objc_msgSend_fpret_debug

    ENTRY _objc_msgSend_fp2ret_debug
    jmp _objc_msgSend_fp2ret
    END_ENTRY _objc_msgSend_fp2ret_debug


    ENTRY _objc_msgSend_noarg
    jmp _objc_msgSend
    END_ENTRY _objc_msgSend_noarg


    ENTRY _method_invoke

    movq    method_imp(%a2), %r11
    movq    method_name(%a2), %a2
    jmp *%r11
    
    END_ENTRY _method_invoke


    ENTRY _method_invoke_stret

    movq    method_imp(%a3), %r11
    movq    method_name(%a3), %a3
    jmp *%r11
    
    END_ENTRY _method_invoke_stret


.section __DATA,__objc_msg_break
.quad 0
.quad 0

__objc_forward_handler_objc_forward_stret_handler的实现如下:

// Default forward handler halts the process.
__attribute__((noreturn)) 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;

#if SUPPORT_STRET
struct stret { int i[100]; };
__attribute__((noreturn)) struct stret 
objc_defaultForwardStretHandler(id self, SEL sel)
{
    objc_defaultForwardHandler(self, sel);
}
void *_objc_forward_stret_handler = (void*)objc_defaultForwardStretHandler;
#endif

#endif

void objc_setForwardHandler(void *fwd, void *fwd_stret)
{
    _objc_forward_handler = fwd;
#if SUPPORT_STRET
    _objc_forward_stret_handler = fwd_stret;
#endif
}

所以,如果给一个对象发送一个消息(没有实现的方法),而且在父类中也没有找到其实现,就会崩溃,报错信息就是unrecognized selector sent to instance.

objc_setForwardHandler具体调用栈需要通过逆向的手段,可以参考这个Objective-C 消息发送与转发机制原理,下面给出一段关键代码:

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

  Class receiverClass = object_getClass(receiver);
  //会先判断是否有forwardingTargetForSelector实现
  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.
  kill(getpid(), 9);
}

上面代码逻辑如下 :

  1. 调用forwardingTargetForSelector,获取新的target作为receiver,重新进行消息发送阶段,如果没有实现forwardingTargetForSelector,或者新的target为nil,进行下一步
  2. 调用methodSignatureForSelector获取方法签名,如果签名正确,再调用forwardInvocation,执行NSInvocation对象,如果不能响应methodSignatureForSelector方法,进行下一步
  3. 未找到转发对象,执行异常处理

下面是整个消息发送与转发流程图,来自Objective-C 消息发送与转发机制原理

消息发送与转发路径流程图.jpg

汇编知识有待加强,逆向也要搞一下,路还很长

你可能感兴趣的:(Runtime-消息发送与转发)