消息发送之 动态方法决议&消息转发

在上篇文章中 objc_msgSend 消息发送之慢速查找我们知道了再慢速也找不到的情况下会进入动态方法解析

IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
 上面代码及 循环体 省略...
/// 未找到实现。尝试方法解析器一次。
    // No implementation found. Try method resolver once.
    if (slowpath(behavior & LOOKUP_RESOLVER)) {
        behavior ^= LOOKUP_RESOLVER;
        return resolveMethod_locked(inst, sel, cls, behavior);
    }

在整个继承链都为找到的情况下尝试动态方法解析一次 只走一次
我们点击去找到这个方法

resolveMethod_locked

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);
       ///如果 整个继承链 都没有找到 当前类方法的实现 还需要 调用一次 实例方法动态方法决议
       ///为什么  因为 元类的父类 是根源类(NSObject)
        ///     根元类的父类指向 类对象(NSObject) 实例方法存才类对象里 所以 需要调用一次 实例方法动态方法决议
    /// 此时为nil 说明 根元类 也没有找到 又因根源类super指向了 NSObject 类对象 所以需要进行 一次 类对象的动态方法决议
        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);
}

判断 是否是元类类对象 并调用不同方法 并return lookUpImpOrForward 函数再次慢速查找一次

为什么元类里还需要调用一次 resolveInstanceMethod
1、-方法(实例方法) 存在 类对象里 +方法(类方法) 存在元类里。
2、这是因为 superclass 走位决定的 根元类 的父类指向了 类对象
如果一个继承 NSObject的类LGPerson声明了一个+(void)sayHello 方法 并未实现
此时我们在给NSObject类对象添加一个Category NSObject +Category 实现 其 一个实例方法 - (void)sayHello 并打印 NSLog(@"hello") 请问会调用吗 这也就解释了 下图圈红的部分

截屏2020-09-27 下午3.24.52.png

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 SEL
    SEL resolve_sel = @selector(resolveInstanceMethod:);
/// 将此方法 作为 参数 传入并慢速通过整个继承链查找此方法有没有实现
 
/// 肯定能找到 因为 NSObject 根类 实现了 (先从缓存中快速查找并判断imp是否为真 为真跳出  否则 在从继承链中找)找到并放入缓存
/// 1、首先判断根类(NSObject)的子类存不存在 resolveInstanceMethod 的方法编号
/// 2、如子类 存在此方法编号 去 cache中通过 sel 查询imp 如果存在 返回imp
/// 3、如果子类只存在了方法编号(如空方法) 没有具体实现也就是imp不为真 再次进行 整个继承链的遍历向上查找
/// 4、肯定能找到 因为 NSObject 实现了 找到后并将其放入缓存
    if (!lookUpImpOrNil(cls, resolve_sel, cls->ISA())) {
        ///这里不知道什么时候会调用
        // Resolver not implemented.
        return;
    }
    
/// 获取objc_msgSend进行强转   * id objc_msgSend(id self, SEL _cmd, ...);
    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
/// objc_msgSend快速发送 调用 resolveInstanceMethod 消息 resolve_sel:可以找到 sel:未实现找不到
    bool resolved = msg(cls, resolve_sel, sel);

    // Cache the result (good or bad) so the resolver doesn't fire next time. 5Z 
    // +resolveInstanceMethod adds to self a.k.a. cls
/// 此处继续慢速查找目的是为了下面抛异常 调用的方法 (如环境变量OBJC_PRINT_RESOLVED_METHODS 打开的话)
    IMP imp = lookUpImpOrNil(inst, sel, cls);
    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));
        }
    }
}

  • 实例方法在类对象中我们都知道 所以 runtime动态方法决议 也区分开来
  • 首先创建一个resolveInstanceMethod SEL 并 将此sel 传进lookUpImpOrNil 慢速查找(优先根据sel去缓存中查找imp imp为真直接返回imp 不为真整个继承链查找找到存入缓存 )
    1、首先判断根类(NSObject)的子类存不存在 resolveInstanceMethod方法编号(sel)
    2、如子类 存在此方法编号 去 cache中通过 sel查询imp 如果存在 返回imp
    3、如果子类只存在了方法编号(如空方法) 没有具体实现也就是imp不为真 再次进行 整个继承链的遍历向上查找
    4、肯定能找到 因为 NSObject 实现了 找到后并将其放入缓存
  • 缓存好方法后 并开始objc_msgSend 快速查找 resolveInstanceMethod 消息 找到后执行它的具体实现 (如我们实现了resolveInstanceMethod 这里执行的就是我们实现的方法会调用)
  • 当前方法再次执行 lookUpImpOrNil 整个继承链的慢速查找看我们 动态添加上方法没有 如找到了 将其存入缓存 并返回imp 没有找到 返回 forward_imp 准备转发

resolveClassMethod

static void resolveClassMethod(id inst, SEL sel, Class cls)
{
    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    ASSERT(cls->isMetaClass());
/// 将resolveClassMethod 再整个继承链中寻找是否存才 此方法 这里肯定不会进去因为根类(NSObject)实现了
    if (!lookUpImpOrNil(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);
        }
    }
    ///resolveClassMethod 进行快速查找流程
    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 = lookUpImpOrNil(inst, sel, cls);

    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));
        }
    }
}
  • +方法(类方法)元类 所以 runtime动态方法决议 也区分开来 具体不在分析和上面resolveInstanceMethod大同小异

消息转发

在源码里知道了动态方法决议的信息 但是 转发我们再也找不到其相关的线索 所以下面我们使用两种方式来寻找其后续的流程

instrumentObjcMessageSends

  • 关于我们怎么找到它的 通过lookUpImpOrForward --> log_and_fill_cache --> logMessageSend,在其源码下面我们看到了instrumentObjcMessageSends的源码实现
void instrumentObjcMessageSends(BOOL flag)
{
    bool enable = flag;

    // Shortcut NOP
    if (objcMsgLogEnabled == enable)
        return;

    // If enabling, flush all method caches so we get some traces
    if (enable)
        _objc_flush_caches(Nil);

    // Sync our log file
    if (objcMsgLogFD != -1)
        fsync (objcMsgLogFD);

    objcMsgLogEnabled = enable;
}
  • 通过logMessageSend源码,了解到消息发送打印信息存储在/tmp/msgSends 目录,如下所示
bool logMessageSend(bool isClassMethod,
                    const char *objectsClass,
                    const char *implementingClass,
                    SEL selector)
{
    char    buf[ 1024 ];

    // Create/open the log file
    if (objcMsgLogFD == (-1))
    {
        snprintf (buf, sizeof(buf), "/tmp/msgSends-%d", (int) getpid ());
        objcMsgLogFD = secure_open (buf, O_WRONLY | O_CREAT, geteuid());
        if (objcMsgLogFD < 0) {
            // no log file - disable logging
            objcMsgLogEnabled = false;
            objcMsgLogFD = -1;
            return true;
        }
    }

    // Make the log entry
    snprintf(buf, sizeof(buf), "%c %s %s %s\n",
            isClassMethod ? '+' : '-',
            objectsClass,
            implementingClass,
            sel_getName(selector));

    objcMsgLogLock.lock();
    write (objcMsgLogFD, buf, strlen(buf));
    objcMsgLogLock.unlock();

    // Tell caller to not cache the method
    return false;
}

  • 开启
    1、不要在源码工程里面我们新建一个项目(否则崩溃日志为空)
    2、 在main中通过extern 声明instrumentObjcMessageSends方法
    3、 打开 objcMsgLogEnabled 开关,即调用instrumentObjcMessageSends方法时,传入YES
    4、运行并找到 /tmp/msgSends目录打开
///LGPerson.h
@interface LGPerson : NSObject
-(void)sayhello;
@end

///LGPerson.m
#import "LGPerson.h"
@implementation LGPerson
@end

/// main.m
extern instrumentObjcMessageSends(BOOL flag);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        LGPerson * p =  [LGPerson alloc];

        instrumentObjcMessageSends(YES);
        [p sayhello];
        instrumentObjcMessageSends(NO);
 
        NSLog(@"Hello, World!");
    }
    return 0;
}

运行 进入/tmp/msgSends打开 也可以看到生成的一些日志

截屏2020-09-28 下午2.00.25.png

继续向下探索 我们在崩溃 后输入bt 看看此时 调用堆栈

截屏2020-09-28 下午2.13.41.png

发现 doesNotRecognizeSelector 我们搜索源码

// Replaced by CF (throws an NSException)
+ (void)doesNotRecognizeSelector:(SEL)sel {
    _objc_fatal("+[%s %s]: unrecognized selector sent to instance %p", 
                class_getName(self), sel_getName(sel), self);
}

// Replaced by CF (throws an NSException)
- (void)doesNotRecognizeSelector:(SEL)sel {
    _objc_fatal("-[%s %s]: unrecognized selector sent to instance %p", 
                object_getClassName(self), sel_getName(sel), self);
}

发现了很眼熟的报错 在报错之前还有两个方法 __forwarding_prep_0_____forwarding___他们在CoreFoundation库中 这意味这我们无法找到其源码.

Hopper/IDA反汇编

由于 CoreFoundation 没有开源,我们可以通过 Hopper Disassembler 工具来反编译 CoreFoundation 的可执行文件,通过阅读反汇编伪代码的方式来继续探索。

如何使用
  • 通过image list,读取整个镜像文件,然后搜索CoreFoundation,查看其可执行文件的路径


    截屏2020-09-28 下午2.33.46.png
  • 前往路径找到CoreFoundation 拖进hopperDisassembler 开始分析

  • 全局搜索 __forwarding_prep_0___方法

    截屏2020-09-28 下午2.39.26.png

    *我的天找到了 继续点击 ____forwarding___进入

int ____forwarding___(int arg0, int arg1) {
   rsi = arg1;
   rdi = arg0;
   r15 = rdi;
   var_30 = *___stack_chk_guard;
   rcx = COND_BYTE_SET(NE);
   if (rsi != 0x0) {
           r12 = _objc_msgSend_stret;
   }
   else {
           r12 = _objc_msgSend;
   }
   rax = rcx;
   rbx = *(r15 + rax * 0x8);
   rcx = *(r15 + rax * 0x8 + 0x8);
   var_140 = rcx;
   r13 = rax * 0x8;
   if ((rbx & 0x1) == 0x0) goto loc_649bb;

loc_6498b:
   rcx = *_objc_debug_taggedpointer_obfuscator;
   rcx = rcx ^ rbx;
   rax = rcx >> 0x1 & 0x7;
   if (rax == 0x7) {
           rcx = rcx >> 0x4;
           rax = (rcx & 0xff) + 0x8;
   }
   if (rax == 0x0) goto loc_64d48;

loc_649bb:
   var_148 = r13;
   var_138 = r12;
   var_158 = rsi;
   rax = object_getClass(rbx);
   r12 = rax;
   r13 = class_getName(rax);
   r14 = @selector(forwardingTargetForSelector:);  
/// 是否实现了 forwardingTargetForSelector:这个方法 未实现跳走
   if (class_respondsToSelector(r12, r14) == 0x0) goto loc_64a67;
loc_649fc:
   rdi = rbx;
/// 实现 向其发送消息 
   rax = _objc_msgSend(rdi, r14);
   if ((rax == 0x0) || (rax == rbx)) goto loc_64a67;

loc_64a19:

判断是否实现了forwardingTargetForSelector这个方法 没有实现继续跳转 loc_64a67


loc_64a67:
    var_138 = rbx;
    if (strncmp(r13, "_NSZombie_", 0xa) == 0x0) goto loc_64dc1;

loc_64a8a:

    rbx = @selector(methodSignatureForSelector:);
    r14 = var_138;
    var_148 = r15;
 ///是否实现了 methodSignatureForSelector  
  if (class_respondsToSelector(r12, rbx) == 0x0) goto loc_64dd7;

loc_64ab2:
    rax = _objc_msgSend(r14, rbx);
    rbx = var_158;
    if (rax == 0x0) goto loc_64e3c;

loc_64ad5:

判断是否实现了methodSignatureForSelector 没有实现跳转 loc_64dd7-> loc_64e3c

loc_64dd7:
    rbx = class_getSuperclass(r12);
    r14 = object_getClassName(r14);
    if (rbx == 0x0) {
            _CFLog(0x4, @"*** NSForwarding: warning: object %p of class '%s' does not implement methodSignatureForSelector: -- did you forget to declare the superclass of '%s'?", var_138, r14, object_getClassName(var_138), r9, stack[2003]);
    }
    else {
            _CFLog(0x4, @"*** NSForwarding: warning: object %p of class '%s' does not implement methodSignatureForSelector: -- trouble ahead", var_138, r14, r8, r9, stack[2003]);
    }
    goto loc_64e3c;

loc_64dc1:
    ____forwarding___.cold.1(var_138, r13, var_140, rcx, r8);
    goto loc_64dd7;
}
loc_64e3c:
    rax = sel_getName(var_140);
    r14 = rax;
    rax = sel_getUid(rax);
    if (rax != var_140) {
            r8 = rax;
            _CFLog(0x4, @"*** NSForwarding: warning: selector (%p) for message '%s' does not match selector known to Objective C runtime (%p)-- abort", var_140, r14, r8, r9, stack[2003]);
    }
    rbx = @selector(doesNotRecognizeSelector:);
    if (class_respondsToSelector(object_getClass(var_138), rbx) == 0x0) {
            ____forwarding___.cold.2(var_138);
    }
    rax = _objc_msgSend(var_138, rbx);
    asm{ ud2 };
    return rax;

methodSignatureForSelector方法响应了 并进行 forwardInvocation方法的处理

loc_64c19:
    r15 = @selector(forwardInvocation:);
    if (class_respondsToSelector(object_getClass(r14), r15) == 0x0) goto loc_64ec2;

loc_64c3b:
    rax = [NSInvocation _invocationWithMethodSignature:r12 frame:var_148];
    r13 = rax;
    _objc_msgSend(0x0, r15);
    var_140 = 0x0;
    r14 = 0x0;
    goto loc_64c76;

loc_64ec2:
    rdi = var_130;
    ____forwarding___.cold.3(rdi, r14);
    goto loc_64ed1;

所以通过上面两种方式 我们知道了 动态方法决议之后 又调用了3个方法

  • 快速转发 forwardingTargetForSelector
  • 慢速转发
    1、methodSignatureForSelector
    2、forwardInvocation

hop____forwarding___ 分析图(自我分析图)
CoreFoundation消息转发hop分析.jpg

实际操作

首先我们进行第一步动态方法决议
示例1:main函数调用 LGPerson的 实例方法sayhello(只声明并不实现) 动态决议方法 返回YES

#import "LGPerson.h"

@implementation LGPerson
 
+(BOOL)resolveInstanceMethod:(SEL)sel{
    NSLog(@"来了%s",  _cmd);
    return  YES;
    
}

@end

运行
截屏2020-09-29 下午4.34.46.png
表象存疑:

1、发现 resolveInstanceMethod 调用了两次 ? 为什么(此问题留到最后分析)
2、发现 就算是 return YES,项目还是会蹦

示例2:通过堆栈信息hop CoreFoundation ____forwarding___ 方法 我们知道它会进入快速转发流程 下面实现转发

#import "LGPerson.h"

@implementation LGPerson
 
+(BOOL)resolveInstanceMethod:(SEL)sel{
    NSLog(@"1 来了%s,%s",sel,_cmd);
    return  YES;
}
-(id)forwardingTargetForSelector:(SEL)aSelector{
    NSLog(@"2 来了%s,%s",aSelector,_cmd);
    return  [super forwardingTargetForSelector:aSelector];;
}
@end
运行:
截屏2020-09-29 下午5.48.18.png
表象存疑:

1、两次的调用有一次在 forwardingTargetForSelector之后调用的 为什么
2、此时我们将快速消息转发 return父类的forwardingTargetForSelector没有做任何处理,它还是蹦了。

分析:

看到这里我们不由得想起了hop源码 及流程图

/// 是否实现了 forwardingTargetForSelector:这个方法 未实现跳走
   if (class_respondsToSelector(r12, r14) == 0x0) goto loc_64a67;
loc_649fc:
   rdi = rbx;
/// 实现 向其发送消息 
   rax = _objc_msgSend(rdi, r14);
///重点
   if ((rax == 0x0) || (rax == rbx)) goto loc_64a67;

1、首先判断是否实现了 forwardingTargetForSelector 方法
2、如实现了进入_objc_msgSend发送其消息,并拿到其返回值imp
3、分析 imp地址 是否为 或者 (返回值是否为当前类)跳转 (我这里返回的是super 的实现nil 也可以尝试返回 self 所以继续会跳到慢速消息转发

loc_64a67:
    var_138 = rbx;
   ///是否为僵尸对象  是调走 不是 向下执行
    if (strncmp(r13, "_NSZombie_", 0xa) == 0x0) goto loc_64dc1;

loc_64a8a:
   
    rbx = @selector(methodSignatureForSelector:);
    r14 = var_138;
    var_148 = r15;
///判断是否响应    methodSignatureForSelector
 if (class_respondsToSelector(r12, rbx) == 0x0) goto loc_64dd7;

4、判断是否为僵尸指针 当前不是往下执行
5、判断是否响应了 methodSignatureForSelector 没响应跳转 (当前环境没有写)

loc_64e3c:
    rax = sel_getName(var_140);
    r14 = rax;
    rax = sel_getUid(rax);
    if (rax != var_140) {
            r8 = rax;
            _CFLog(0x4, @"*** NSForwarding: warning: selector (%p) for message '%s' does not match selector known to Objective C runtime (%p)-- abort", var_140, r14, r8, r9, stack[2003]);
    }
    rbx = @selector(doesNotRecognizeSelector:);
    if (class_respondsToSelector(object_getClass(var_138), rbx) == 0x0) {
            ____forwarding___.cold.2(var_138);
    }
    rax = _objc_msgSend(var_138, rbx);
    asm{ ud2 };
    return rax;
///跳这来   ///装载错误及警告 信息准备抛出
loc_64dd7:
    rbx = class_getSuperclass(r12);
    r14 = object_getClassName(r14);
    if (rbx == 0x0) {
            _CFLog(0x4, @"*** NSForwarding: warning: object %p of class '%s' does not implement methodSignatureForSelector: -- did you forget to declare the superclass of '%s'?", var_138, r14, object_getClassName(var_138), r9, stack[2003]);
    }
    else {
            _CFLog(0x4, @"*** NSForwarding: warning: object %p of class '%s' does not implement methodSignatureForSelector: -- trouble ahead", var_138, r14, r8, r9, stack[2003]);
    }
    goto loc_64e3c;
总结1:
  • 动态方法决议方法 判断的并不是 返回 YES NO 而是 看当前整个继承链向上查找,有没有动态的添加上 sel, 如果整个继承链没有找到的话 那么会进行下面的 快速消息转发
  • forwardingTargetForSelector快速消息转发 指的是 将上面未处理的消息 ,重定向一个 消息接受者,并将未实现的消息 实现(需要一模模一样样)。返回值就是重定向消息者。(如返回了一个消息接受者,但是只声明了 并未实现 ,则同样会进入重定向的·方法决议-消息转发(快 慢)-崩溃) 如果返回nil 或者 返回的还是当前 接受者 直接进入下面方法 判断 及慢速转发流程

可是为什么会调用两次?我们继续将代码 写上一个 forwardingTargetForSelector 并运行

+(BOOL)resolveInstanceMethod:(SEL)sel{
    NSLog(@"1 来了%s,%s",sel,_cmd);
    return  NO;
}
-(id)forwardingTargetForSelector:(SEL)aSelector{
    NSLog(@"2 来了%s,%s",aSelector,_cmd);
    return  [super forwardingTargetForSelector:aSelector];;
}
//
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    NSLog(@"3 来了%s,%s",aSelector,_cmd);
    return  [NSMethodSignature signatureWithObjCTypes:"v@:"];
}

截屏2020-10-09 上午11.34.32.png
表象存疑:

1、 发现实现了methodSignatureForSelector 依旧会调用两次此时的sel变为了_forwardStackInvocation:
2、程序崩溃了
3、_forwardStackInvocation: 是什么? 在伪代码的分析图里我们见过。

分析

按照上面我们分析的源码图以及下面的源码


loc_64ad5:
    r12 = rax;
    rax = [rax _frameDescriptor];
    r13 = rax;
    if (((*(int16_t *)(*rax + 0x22) & 0xffff) >> 0x6 & 0x1) != rbx) {
            rax = sel_getName(var_-320);
            rdx = " not";
            rdx = rax;
            _CFLog(0x4, @"*** 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.", rdx);
    }
    var_-336 = r13;
///是否响应  _forwardStackInvocation: 方法 
   if (class_respondsToSelector(object_getClass(r14), @selector(_forwardStackInvocation:)) == 0x0) goto loc_64c19;

///响应 将 转发的sel封装在NSInvocation中
loc_64b6c:
    if (*____forwarding___.onceToken != 0xffffffffffffffff) {
            dispatch_once(____forwarding___.onceToken, ^ { /* block implemented at ______forwarding____block_invoke */ });
    }
    r15 = [NSInvocation requiredStackSizeForSignature:r12];
    rsi = *____forwarding___.invClassSize;
    rsp = rsp - ___chkstk_darwin();
    r13 = rsp;
    __bzero(r13, rsi);
    rbx = rsp - ___chkstk_darwin();
    objc_constructInstance(*____forwarding___.invClass, r13);
    var_-320 = r15;
    [r13 _initWithMethodSignature:r12 frame:var_-328 buffer:rbx size:r15];
    [var_-312 _forwardStackInvocation:r13];
    r14 = 0x1;
    goto loc_64c76;

loc_64c76:
    if (*(int8_t *)(r13 + 0x34) != 0x0) {
            rax = *var_-336;
            if (*(int8_t *)(rax + 0x22) < 0x0) {
                    rcx = *(int32_t *)(rax + 0x1c);
                    rdx = *(int8_t *)(rax + 0x20) & 0xff;
                    memmove(*(rdx + var_-328 + rcx), *(rdx + rcx + *(r13 + 0x8)), *(int32_t *)(*rax + 0x10));
            }
    }
    rax = [r12 methodReturnType];
    rbx = rax;
    rax = *(int8_t *)rax;
    if ((rax != 0x76) && (((rax != 0x56) || (*(int8_t *)(rbx + 0x1) != 0x76)))) {
            r15 = *(r13 + 0x10);
            if (r14 != 0x0) {
                    r15 = [[NSData dataWithBytes:r15 length:var_-320] bytes];
                    [r13 release];
                    rax = *(int8_t *)rbx;
            }
            if (rax == 0x44) {
                    asm{ fld        tword [r15] };
            }
    }
    else {
            r15 = ____forwarding___.placeholder;
            if (r14 != 0x0) {
                    [r13 release];
            }
    }
    goto loc_64d82;

loc_64c19:

大概我们可以看明白 如实现了 _forwardStackInvocation 会封装一个NSInvocation对象返回 我们实现它-(void)_forwardStackInvocation:(NSInvocation *)anInvocation并运行

+(BOOL)resolveInstanceMethod:(SEL)sel{
    NSLog(@"1 来了%s,%s",sel,_cmd);
     return NO;
}

-(id)forwardingTargetForSelector:(SEL)aSelector{
    NSLog(@"2 来了%s,%s",aSelector,_cmd);
    return [super forwardingTargetForSelector:aSelector];
}
//
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    NSLog(@"3 来了%s,%s",aSelector,_cmd);
    return  [NSMethodSignature signatureWithObjCTypes:"v@:"];

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

}
截屏2020-10-13 下午3.19.37.png

程序完美运行 也不再调用两次了

总结2
  • 慢速转发流程
    1、实现方法签名 - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
    2、判断是否实现 _forwardStackInvocation:(NSInvocation *)anInvocation
    ----实现了:CoreFoundation 将需要转发的SEL封装在参数 NSInvocation 中 并调用 _forwardStackInvocation:(NSInvocation *)anInvocation 转发成功
    ----未实现:再次调用动态方法决议(resolveInstanceMethod/resolveClassMethod)一次 此时的sel_forwardStackInvocation.

我们屏蔽掉 _forwardStackInvocation:(NSInvocation *)anInvocation 实现 - (void)forwardInvocation:(NSInvocation *)anInvocation并运行

 
+(BOOL)resolveInstanceMethod:(SEL)sel{
    NSLog(@"1 来了%s,%s",sel,_cmd);
     return NO;
}
-(id)forwardingTargetForSelector:(SEL)aSelector{
    NSLog(@"2 来了%s,%s",aSelector,_cmd);
    return [super forwardingTargetForSelector:aSelector];
}
//
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    NSLog(@"3 来了%s,%s",aSelector,_cmd);
    return  [NSMethodSignature signatureWithObjCTypes:"v@:"];

}
//
//
//-(void)_forwardStackInvocation:(NSInvocation *)anInvocation
//{
//    NSLog(@"4 %s - %s",__func__,anInvocation.selector);
//
//}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
    
    NSLog(@"5 %s - %s",__func__,anInvocation.selector);
}
@e
截屏2020-10-13 下午3.44.25.png

程序完美运行

总结3

接上面总结2 如未实现 _forwardStackInvocation:(NSInvocation *)anInvocation 会调用一次动态决议方法 如动态决议还未找到 会判断是否实现 `- (void)forwardInvocation:(NSInvocation *)anInvocation

------如实现 消息转发成功
------ 未实现 崩溃

为什么会调用两次 resolveInstanceMethod

  • 我们在CoreFoundotion框架中的转发流程逻辑并未查找到什么有利信息值到了这里
loc_64a8a:
    rbx = @selector(methodSignatureForSelector:);
    r14 = var_138;
    var_148 = r15;
    if (class_respondsToSelector(r12, rbx) == 0x0) goto loc_64dd7;

loc_64ab2:
    rax = _objc_msgSend(r14, rbx);
    rbx = var_158;
    if (rax == 0x0) goto loc_64e3c;
  • 经过我们上面反复实验 发现 第二次的调用 存才 返回签名方法之后
    1、NSObject [子类]并未实现 methodSignatureForSelector方法时候 我们看到 调用的第二次动态方法决议 resolveInstanceMethod SEL 为sayhello
    2、NSObject [子类]实现了 methodSignatureForSelector 方法的时候 我们看到 调用的第二次动态方法决议 resolveInstanceMethodSEL 为 _forwardStackInvocation:
    此时我们应该想到 在子类没有实现methodSignatureForSelector 方法的时候 一定走了 NSObject方法
    截屏2020-10-13 下午4.37.09.png

在CoreFoundotion 搜索了一NSObject的方法实现伪代码 methodSignatureForSelector 我的天

截屏2020-10-13 下午4.41.13.png

NSObject 的 methodSignatureForSelector方法实现出来了 继续向下点击 ___methodDescriptionForSelector

截屏2020-10-13 下午4.44.11.png

看上面的代码实属难受(有时间的可系统分析),我们看到了上面代码 class_getInstanceMethod 复制 回到我们的objc4 源代码 查看

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);
}
  • 看到这里我们也明白了为什么没有实现 methodSignatureForSelector 会调用两次

进入源代码断点看是否能断在👆所示地方

切到可编译源码环境


@implementation LGPerson
+(BOOL)resolveInstanceMethod:(SEL)sel{
    NSLog(@"1 来了%s,%s",sel,_cmd);
     return NO;
}
-(id)forwardingTargetForSelector:(SEL)aSelector{
    NSLog(@"2 来了%s,%s",aSelector,_cmd);
    return [super forwardingTargetForSelector:aSelector];
}
//
//- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
//{
//    NSLog(@"3 来了%s,%s",aSelector,_cmd);
//    return  [NSMethodSignature signatureWithObjCTypes:"v@:"];
//
//}
////
//
//-(void)_forwardStackInvocation:(NSInvocation *)anInvocation
//{
//    NSLog(@"4 %s - %s",__func__,anInvocation.selector);
//
//}
//- (void)forwardInvocation:(NSInvocation *)anInvocation{
//
//    NSLog(@"5 %s - %s",__func__,anInvocation.selector);
//
//}

先将代码断在此处 在打开 objc4里的地方 以免调用的不是我们的消息 sayhello


截屏2020-10-13 下午5.09.41.png

再打开这里运行
截屏2020-10-13 下午5.13.46.png

截屏2020-10-13 下午5.19.29.png

可以看到在没有实现 methodSignatureForSelector 方法的时候 会走NSObject的方法 并且会调用 class_getInstanceMethod()方法 并调用 lookUpImpOrForward

我们将 methodSignatureForSelector实现 再次还原上述断点并合适时机运行发现 并没有调用 class_getInstanceMethod()方法 此时的sel为_forwardStackInvocation: 而是发送了一次 它的消息而掉起的动态方法决议 具体什么时机 我猜是这里个时机


截屏2020-10-13 下午5.57.41.png

`

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,544评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,430评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,764评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,193评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,216评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,182评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,063评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,917评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,329评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,543评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,722评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,425评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,019评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,671评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,825评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,729评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,614评论 2 353