[OC学习笔记]objc_msgSend(三):动态方法决议和消息转发

一、forward_imp

在上一节,我们讲到了在寻找imp的递归父类过程中,如果父类为nil,那么会执行imp = forward_imp继续寻找。下面我们通过一个例子来学习:
在这里插入图片描述
通常我们在只写方法声明,不写方法实现或者利用函数performSelector:@selector(XXX)但是都没有实现方法的时候,就会报方法找不到错误:unrecognized selector sent to instance XXX。其实这个错误来自于lookUpImpOrForward中的forward_imp方法:

const IMP forward_imp = (IMP)_objc_msgForward_impcache; // 汇编方法

全局搜索objc_msgForward_impcache,可以发现这个方法在汇编里面:

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

	// No stret specialization.
	b	__objc_msgForward

	END_ENTRY __objc_msgForward_impcache

	
	ENTRY __objc_msgForward // 进入 __objc_msgForward

	adrp	x17, __objc_forward_handler@PAGE // 调用__objc_forward_handler方法
	ldr	p17, [x17, __objc_forward_handler@PAGEOFF]
	TailCallFunctionPointer x17 // 返回函数指针
	
	END_ENTRY __objc_msgForward // 结束 __objc_msgForward

__objc_msgForward_impcache里面调用__objc_msgForward方法进入__objc_msgForward,我们先介绍下ADRP指令

  • 编译时,首先会计算出当前PC到exper的偏移量#offset_to_exper
  • pc的低12位清零,然后加上偏移量,给register
  • 得到的地址,是含有label的4KB对齐内存区域的base地址

请添加图片描述

Page 的地址:符号扩展 21 位偏移量,将其向左移 12,并将其添加到 PC 的值中,并清除其底部 12 位,
将结果写入寄存器 Xd。这计算包含标签的4KiB对齐内存区域的基址,并设计为与提供标签地址底部12位的加载,
存储或ADD指令结合使用。这允许使用两条指令对PC的+4GiB内的任何位置进行位置依赖寻址,
前提是动态重新定位的最小粒度为4KiB(即标签地址的底部12位不受重新定位的影响)。
术语“页面”是4KiB重定位颗粒的简写,不一定与虚拟内存页面大小有关。

通俗讲为:ADRP指令可以理解成先进行PC + imm(偏移值)然后找到lable所在的一个4KB的页,然后取得label的基址,再进行偏移去寻址。
这里就是 adrp指令计算出这个__objc_forward_handler@PAGE(偏移量),之后将基址存到寄存器X17中,关键就是__objc_forward_handler 方法。
ldr: LDR R0,[R1, #8];将存储器地址为R1+8的字数据读入寄存器R0。这里实际上将x17又去除偏移量, 给到p17中。
可看到其实关键是调用了一个__objc_forward_handler,我们全局搜索一下可发现是一个C++方法,在objc-runtime.mm中, 看下_objc_forward_handler

/***********************************************************************
* objc_setForwardHandler
**********************************************************************/

#if !__OBJC2__

// Default forward handler (nil) goes to forward:: dispatch.
void *_objc_forward_handler = nil;
void *_objc_forward_stret_handler = nil;

#else

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

可看到报错信息...unrecognized selector sent to instance XXX...就是我们经典的报错方法, 并且其中可看到底层是不区分类方法 +与对象方法/实例方法 -的。“+”/ "-"在这里是人为拼接上的。我们之前在Clang也看过类方法在是元类调用实例方法形式存在的。调用地方需要返回再看lookUpImpOrForward方法,如果父类链都没找到就令imp = forward_imp;最终方法如果没有找到就会往下走return impreturn forward_imp,就会报那个经典方法找不到错误。

二、动态方法决议

仔细查看lookUpImpOrForward源码,imp = forward_imp;赋值后只是单纯break出了循环,并不会直接return。在return前(循环外面)可以看到一个if条件块:

// No implementation found. Try method resolver once.

if (slowpath(behavior & LOOKUP_RESOLVER)) {
    
    
    behavior ^= LOOKUP_RESOLVER;
    return resolveMethod_locked(inst, sel, cls, behavior);
}

这里就是进行动态方法决议的部分。据此,我们可以画出lookUpImpOrForward慢速查找流程图:

请添加图片描述
看了流程图,相信大家都对这个流程又了一定的认识。下面就先看一下resolveMethod_locked的代码:

/***********************************************************************
* resolveMethod_locked
* Call +resolveClassMethod or +resolveInstanceMethod.
*
* Called with the runtimeLock held to avoid pressure in the caller
* Tail calls into lookUpImpOrForward, also to avoid pressure in the callerb
**********************************************************************/
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
    
    // 如果有动态方法决议, 处理了, 就调用lookUpImpOrForwardTryCache重新帮你处理下
    return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);
}

可以这样理解,当前方法都imp没找到,系统给一次补救机会:动态方法决议。

  • 实例方法 -> resolveInstanceMethod
  • 类方法 -> resolveClassMethod

下面先看实例方法的。

(一)实例方法:resolveInstanceMethod

/***********************************************************************
* 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 resolveInstanceMethod(id inst, SEL sel, Class cls)
{
    
    
    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    
    // 定义一个resolveInstanceMethod 的方法编号resolve_sel
    SEL resolve_sel = @selector(resolveInstanceMethod:);
    
    // 判断当前类, 父类等(NSObject默认有决议方法) 是否有决议方法resolveInstanceMethod,
    // 根本没写就直接返回
    if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) {
    
    
        // 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.
    // +resolveInstanceMethod adds to self a.k.a. cls
    // 缓存结果(好或坏),以便下次不会触发解析器。
    IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);

    if (resolved  &&  PrintResolving) {
    
    
        ...
    }
}

动态方法决议其实是调用了一个决议方法,拿实例方法举例就是resolveInstanceMethod,如果当前imp没有, 会去父类链查找resolveInstanceMethodNSObject默认有),若是这个方法里面处理了我们查找的sel、实现、转发等等,那么系统也算你成功并不会报错。

lookUpImpOrNilTryCache

// 定义一个resolveInstanceMethod 的方法编号resolve_sel
SEL resolve_sel = @selector(resolveInstanceMethod:);
// 判断当前类, 父类等(NSObject默认有决议方法) 是否有决议方法resolveInstanceMethod,
// 根本没写就直接返回
if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) {
    
    
    // Resolver not implemented.
    return;
}

定义一个决议方法SEL:resolve_sel,然后走下面if判断,是否存在。lookUpImpOrNilTryCache的第三个参数是:cls->ISA(true)

inline Class
objc_object::ISA(bool authenticated)
{
    
    
    ASSERT(!isTaggedPointer());
    return isa.getDecodedClass(authenticated);
}
inline Class
isa_t::getDecodedClass(bool authenticated) {
    
    
#if SUPPORT_INDEXED_ISA//真机为1, 否则为0
    // 判断是否为nonpointer, 通常已经初始化完毕的类,  isa中nonpointer为nil
    if (nonpointer) {
    
    
        return classForIndex(indexcls);
    }
    // 返回当前类
    return (Class)cls;
#else
    // 非真机getClass( )
    return getClass(authenticated);
#endif
}

请添加图片描述
回到lookUpImpOrNilTryCache

IMP lookUpImpOrNilTryCache(id inst, SEL sel, Class cls, int behavior)
{
    
    
    return _lookUpImpTryCache(inst, sel, cls, behavior | LOOKUP_NIL);
}

可发现它是调用_lookUpImpTryCache方法。

补救

我们看一下补救的措施,在main里:

[me performSelector:@selector(sayHello2)];

实际上没有做sayHello2的声明和实现,但我们实现类方法resolveInstanceMethod:

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    
    
    if (sel == @selector(sayHello2)) {
    
    
        //获取sayHello方法的imp
        IMP imp = class_getMethodImplementation(self, @selector(sayHello));
        //获取sayHello的实例方法
        Method sayMethod  = class_getInstanceMethod(self, @selector(sayHello));
        //获取sayMethod的方法签名
        const char *type = method_getTypeEncoding(sayMethod);
        //将sel的实现指向
        return class_addMethod(self, sel, imp, type);
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

在执行实例方法sayHello2时,在快速、慢速查找都没有找到的情况下,就会执行到resolveInstanceMethod方法。我们可以看到,它正常输出了。

(二)类方法:resolveClassMethod

/***********************************************************************
* 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 resolveClassMethod(id inst, SEL sel, Class cls)
{
    
    
    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    ASSERT(cls->isMetaClass());

    if (!lookUpImpOrNilTryCache(inst, @selector(resolveClassMethod:), cls)) {
    
    
        // Resolver not implemented.
        return;
    }

    Class nonmeta;
    {
    
    
        mutex_locker_t lock(runtimeLock);
        nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);
        // +initialize path should have realized nonmeta already
        if (!nonmeta->isRealized()) {
    
    
            _objc_fatal("nonmeta class %s (%p) unexpectedly not realized",
                        nonmeta->nameForLogging(), nonmeta);
        }
    }
    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(nonmeta, @selector(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 = lookUpImpOrNilTryCache(inst, sel, cls);

    if (resolved  &&  PrintResolving) {
    
    
        ...
    }
}

resolveClassMethodcls本来是元类,通过getMaybeUnrealizedNonMetaClass来获取元类对应的类对象,然后再对类对象发送消息(相当于调用类方法)。

补救:

[me sayNB];

我们调用一个没有实现的类方法,在类的实现里面实现resolveClassMethod方法:

+ (BOOL)resolveClassMethod:(SEL)sel{
    
    
    
    if (sel == @selector(sayNB)) {
    
    
        IMP imp = class_getMethodImplementation(objc_getMetaClass("MyPerson"), @selector(sayFuckOff));
        Method classMethod  = class_getInstanceMethod(objc_getMetaClass("MyPerson"), @selector(sayFuckOff));
        const char *type = method_getTypeEncoding(classMethod);
        return class_addMethod(objc_getMetaClass("MyPerson"), sel, imp, type);
    }
    
    return [super resolveClassMethod:sel];
}

就可以完美补救了。

三、消息转发

如果动态解析(动态决议)阶段还是没有对应的方法,那么就会来到消息转发阶段。消息转发阶段分为两部分,替换消息接收者阶段(快速转发),和完全消息转发阶段(慢速转发)。

(一)快速转发流程

实现forwardingTargetForSelector方法,并返回替换的对象。先command + shift + 0,来打开开发文档进行查阅:
请添加图片描述
翻译一下Discussion部分:
如果对象实现(或继承)此方法,并返回非 nil(和非 self)结果,则返回的对象将用作新的接收方对象,并且消息调度将恢复到该新对象。(显然,如果你从这种方法返回self,代码就会陷入无限循环。)
如果在非 root 类中实现此方法,并且对于给定的选择器,您的类没有任何可返回的内容,则应返回调用 super 的实现的结果。
此方法使对象有机会在更昂贵的 forward 调用之前重定向发送到它的未知消息:机器接管。当您只想将消息重定向到另一个对象时,这很有用,并且可能比常规转发快一个数量级。如果转发的目标是捕获 NSinvocation,或者在转发过程中操作参数或返回值,则此方法没有用处。
能够得知这个方法是一个重定向的过程。我们可以通过实现MyPersonforwardingTargetForSelector:方法来使它成功调用它本身并未实现的方法sayBBB

//第二根稻草,使用快速消息转发,找其他对象来实现方法
- (id)forwardingTargetForSelector:(SEL)aSelector {
    
    
    if (aSelector == @selector(sayBBB)) {
    
    
        return [PersonB alloc];
    }
    return nil;
}

这样就完成了消息转发,而且不像动态方法决议那样臃肿。以上就是快速转发的流程了。

(二)慢速转发流程

若是PersonB类并无实现sayBBB方法呢?那么就会进入到methodSignatureForSelector方法,即慢速转发流程。还是先看一下开发文档:
请添加图片描述
翻译下Discussion部分:
此方法用于协议的实现。此方法还用于必须创建 NSinvocation 对象的情况,例如在消息转发期间。如果对象维护委托或能够处理它不直接实现的消息,则应重写此方法以返回适当的方法签名。
再看Return Value部分,可以知道这是一个返回方法签名的过程。
那么我们在类中先重写methodSignatureForSelector方法,根据开发文档可知这个方法须要搭配NSInvocation,以及返回适当的方法签名,即NSMethodSignature

//第三根稻草,使用完全消息转发
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    
    
    if (aSelector == @selector(sayBBB)) {
    
    
//        NSMethodSignature *sig = [NSMethodSignature methodSignatureForSelector:aSelector];
        NSMethodSignature *sig = [NSMethodSignature signatureWithObjCTypes:"v@:"];
        return sig;
    }
    return nil;
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    
    
    
    SEL aSelector = [anInvocation selector];
    if (aSelector == @selector(sayBBB)) {
    
    
        anInvocation.selector = @selector(sayAAA);
        [anInvocation invokeWithTarget:[PersonB alloc]];
    } else {
    
    
        [super forwardInvocation:anInvocation];
    }

}

四、查看消息转发流程

虽然我们对消息转发流程以及有所了解了,可是咱们并无在源码中看到调用的过程,那么究竟是怎么被调用的呢?在崩溃界面使用bt指令查看堆栈。
请添加图片描述
我们在Xcode的包里面可以找到CoreFoundation的可执行文件,我们下载工具“Hopper”的试用版帮助我们反汇编。搜索___forwarding_prep_0___可以看到:

int ___forwarding_prep_0___(int arg0, int arg1, int arg2, int arg3, int arg4, int arg5, int arg6, int arg7) {
    
    
    r29 = &saved_fp;
    var_10 = q7;
    var_20 = q6;
    var_30 = q5;
    var_40 = q4;
    var_50 = q3;
    var_60 = q2;
    var_70 = q1;
    var_80 = q0;
    var_90 = r8;
    var_98 = arg7;
    var_A0 = arg6;
    var_A8 = arg5;
    var_B0 = arg4;
    var_B8 = arg3;
    var_C0 = arg2;
    var_C8 = arg1;
    r0 = ____forwarding___(&var_D0, 0x0);
    if (r0 != 0x0) {
    
    
            r0 = *r0;
    }
    else {
    
    
            r0 = objc_msgSend(var_D0, var_C8);
    }
    return r0;
}

双击进入____forwarding___,下面就保留主要代码了:

void ____forwarding___(int arg0, int arg1) {
    
    
    r29 = &saved_fp;
    r31 = r31 + 0xffffffffffffffa0 - 0x110;
    r19 = &var_160;
    r25 = arg1;
    r20 = arg0;
    r21 = *(int128_t *)arg0;
    r22 = *(int128_t *)(arg0 + 0x8);
    if ((r21 & 0xffffffff80000000) == 0x0) goto loc_119f20;

	...

loc_119f20:
    r0 = object_getClass(r21);
    r23 = r0;
    r24 = class_getName(r0);
    if (class_respondsToSelector(r23, @selector(forwardingTargetForSelector:)) == 0x0) goto loc_119f68;//⚠️
// 先判断是否有forwardingTargetForSelector方法。
// 若是forwardingTargetForSelector方法存在,即进入快速转发流程,
// 调用forwardingTargetForSelector方法
// 否则goto loc_119f68
loc_119f4c:
    r0 = [r21 forwardingTargetForSelector:r2];
    if (r0 != 0x0) {
    
    
            asm {
    
     ccmp       x0, x21, #0x4, ne };
    }
    if (CPU_FLAGS & NE) goto loc_11a200;

loc_119f68://⚠️
    if (strncmp(r24, "_NSZombie_", 0xa) == 0x0) goto loc_11a2c0;
// 判断是不是僵尸对象,不是则继续
loc_119f80:
    if ((class_respondsToSelector(r23, @selector(methodSignatureForSelector:)) & 0x1) == 0x0) goto loc_11a32c;//⚠️⚠️⚠️
// 如果methodSignatureForSelector:实现了继续往下走到forwardInvocation:
// 如果methodSignatureForSelector:没找到就执行了doesNotRecognizeSelector:
loc_119f98:
    r0 = [r21 methodSignatureForSelector:r2];
    if (r0 == 0x0) goto loc_11a398;

...


loc_11a0ec:
    if (class_respondsToSelector(object_getClass(r21), @selector(forwardInvocation:)) == 0x0) goto loc_11a374;

loc_11a108:
    r25 = [NSInvocation _invocationWithMethodSignature:r23 frame:r20];
    [r21 forwardInvocation:r2];
    r26 = 0x0;
    goto loc_11a13c;

loc_11a374:
    ____forwarding___.cold.4(r19 + 0x0, r21);
    return;

loc_11a398://⚠️⚠️
    r0 = sel_getName(r22);
    r20 = r0;
    r0 = sel_getUid(r0);
    if (r0 != r22) {
    
    
            _CFLog(0x4, @"*** NSForwarding: warning: selector (%p) for message '%s' does not match selector known to Objective C runtime (%p)-- abort");
    }
    if (class_respondsToSelector(object_getClass(r21), @selector(doesNotRecognizeSelector:)) != 0x0) {
    
    
            r1 = @selector(doesNotRecognizeSelector:);
            r0 = objc_msgSend(r21, r1);
            asm {
    
     brk        #0x1 };
            loc_11a400(r0, r1);
    }
    else {
    
    
            ____forwarding___.cold.3(r21);
    }
    return;

loc_11a32c://⚠️⚠️⚠️
    r23 = class_getSuperclass(r23);
    r20 = object_getClassName(r21);
    if (r23 == 0x0) {
    
    
            object_getClassName(r21);
            _CFLog(0x4, @"*** NSForwarding: warning: object %p of class '%s' does not implement methodSignatureForSelector: -- did you forget to declare the superclass of '%s'?");
            r31 = (r31 - 0x20) + 0x20;
    }
    else {
    
    
            _CFLog(0x4, @"*** NSForwarding: warning: object %p of class '%s' does not implement methodSignatureForSelector: -- trouble ahead");
            r31 = r31 + 0x0;
    }
    goto loc_11a398;//⚠️⚠️

loc_11a2c0:
    ____forwarding___.cold.2(r21, r24, r22);
    return;

...

全部的没有找到相对应的方法最终都会执行doesNotRecognizeSelector方法。不管是_forwardStackInvocation:方法(上面省略了)还是forwardInvocation:方法,都能goto loc_11a13c

loc_11a13c:
    if (*(int8_t *)(r25 + 0x34) != 0x0) {
    
    
            r8 = *r24;
            if ((*(int8_t *)(r8 + 0x22) & 0x80) != 0x0) {
    
    
                    r9 = *(r25 + 0x8);
                    r10 = *(int32_t *)(r8 + 0x1c);
                    r12 = *(int8_t *)(r8 + 0x20);
                    memmove(*(r12 + r20 + r10), *(r12 + r9 + r10), *(int32_t *)(*r8 + 0x10));
            }
    }
    r0 = [r23 methodReturnType];
    r8 = *(int8_t *)r0;
    if (r8 != 0x76 && (r8 != 0x56 || *(int8_t *)(r0 + 0x1) != 0x76)) {
    
    
            r20 = *(r25 + 0x10);
            if (r22 != 0x0) {
    
    
                    r20 = [[NSData dataWithBytes:r20 length:r26] bytes];
                    [r25 release];
            }
    }
    else {
    
    
            if (r22 != 0x0) {
    
    
                    [r25 release];
            }
            r20 = 0x694620;
    }
    goto loc_11a26c;

loc_11a26c:
    if (**___stack_chk_guard != **___stack_chk_guard) {
    
    
            __stack_chk_fail();
    }
    return;

应该就是直接把结果返回了。在反汇编流程中能够看到系统调用了一个_forwardStackInvocation方法,这个方法并无对外暴露,在forwardInvocation:方法前面被判断调用,我们试着重写一下看看效果:

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    
    
    NSLog(@"%s %@", __func__, NSStringFromSelector(anInvocation.selector));
}
- (void)_forwardStackInvocation:(NSInvocation *)anInvocation {
    
    
    NSLog(@"%s %@", __func__, NSStringFromSelector(anInvocation.selector));
}

输出:

-[MyPerson _forwardStackInvocation:] sayBBB

确实,有_forwardStackInvocation方法的时候就不会再走forwardInvocation:方法了。

动态决议方法为什么走两次

细心的小伙伴可能已经发现了,在resolveInstanceMethod:打断点,发现它走两遍。我们在objc源码的对象动态方法决议(resolveInstanceMethod)里面打上断点,bt查看堆栈:
请添加图片描述
发现第二次进来是由于在CoreFoundation库中的methodSignatureForSelector方法里的__methodDescriptionForSelector方法调用了objc库中的class_getInstanceMethod方法。
真正的methodSignatureForSelector源码是在CoreFoundation库中,去反汇编中进行查看:

int -[NSObject methodSignatureForSelector:]() {
    
    
    var_10 = r20;
    stack[-24] = r19;
    r31 = r31 + 0xffffffffffffffe0;
    saved_fp = r29;
    stack[-8] = r30;
    if (r2 != 0x0) {
    
    
            r0 = objc_opt_class();
            r1 = r2;
            if (___methodDescriptionForSelector(r0, r1) != 0x0) {
    
    
                    r0 = [NSMethodSignature signatureWithObjCTypes:r1];
            }
            else {
    
    
                    r0 = 0x0;
            }
    }
    else {
    
    
            r0 = 0x0;
    }
    return r0;
}

发现确实有__methodDescriptionForSelector方法,进一步跟进:

int ___methodDescriptionForSelector(int arg0, int arg1) {
    
    
    r31 = r31 - 0x60;
    var_40 = r26;
    stack[-72] = r25;
    var_30 = r24;
    stack[-56] = r23;
    var_20 = r22;
    stack[-40] = r21;
    var_10 = r20;
    stack[-24] = r19;
    saved_fp = r29;
    stack[-8] = r30;
    r19 = arg1;
    r20 = arg0;
    if (arg0 == 0x0) goto loc_12566c;

...

loc_12566c:
    r0 = class_getInstanceMethod(r20, r19);
    if (r0 != 0x0) {
    
    
            r0 = method_getDescription(r0);
            r24 = *(int128_t *)r0;
            r23 = *(int128_t *)(r0 + 0x8);
    }
    else {
    
    
            r23 = 0x0;
            r24 = 0x0;
    }
    goto loc_125690;

...

}

发现确实调用了class_getInstanceMethod方法,再去objc源码中进行查看:

/***********************************************************************
* class_getInstanceMethod.  Return the instance method for the
* specified class and selector.
**********************************************************************/
Method class_getInstanceMethod(Class cls, SEL sel)
{
    
    
    if (!cls  ||  !sel) return nil;

    // This deliberately avoids +initialize because it historically did so.

    // This implementation is a bit weird because it's the only place that 
    // wants a Method instead of an IMP.

#warning fixme build and search caches
        
    // Search method lists, try method resolver, etc.
    lookUpImpOrForward(nil, sel, cls, LOOKUP_RESOLVER);

#warning fixme build and search caches

    return _class_getMethod(cls, sel);
}

发现的确调用了lookUpImpOrForward函数,因此动态方法决议会被调用2次。

五、动态方法决议和消息转发总结

在网上看到这张图不错,拿来总结一下吧。
请添加图片描述

参考博客:
http://www.javashuo.com/article/p-fnmnrbei-ws.html
https://juejin.cn/post/7094940251419836430#heading-5
https://blog.csdn.net/miaocuilin/article/details/118502088
https://blog.csdn.net/weixin_40918107/article/details/108811627

猜你喜欢

转载自blog.csdn.net/weixin_52192405/article/details/125143410