美文网首页
objc_msgSend调用流程

objc_msgSend调用流程

作者: 浪的出名 | 来源:发表于2020-09-23 17:52 被阅读0次

    oc的方法调用

    • OC中的方法调用其实都是转成了objc_msgSend函数的调用,给receiver(方法调用者)发送了一条消息(selector方法名)
    • 通过xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc 文件名命令将目标文件转换为.cpp文件也可以看出方法调用的本质就是objc_msgSend(receiver,sel)

    objc_msgSend缓存快速查找流程

    • 通过源码查找objc_msgSend的实现,全局搜索objc_msgSend并没有找到它的实现,在.s(汇编代码)中找到了_objc_msgSend,所以objc_msgSend的底层是用汇编实现的,用汇编有更快的处理速度及参数不确定性影响的优势。

    汇编的执行效率大于c/c++,oc方法调用是比较频繁的所以底层用汇编实现提高性能

    • objc_msgSend的cache调用流程图如下


      cache方法快速查找流程-2.png

    objc_msgSend慢速方法列表查找

    • 在上文查找到MethodTableLookup,继续执行到bl _lookUpImpOrForward
    • 继续查找_lookUpImpOrForward,发现找不到_lookUpImpOrForward,因为_lookUpImpOrForward不是通过汇编实现的,可以去掉前面的下划线查找,或者通过汇编断点
      image.png
    • objc_msgSend慢速方法列表查找流程图如下


      慢速查找.png
    • 我们发现如果没有实现动态方法解析及后面的消息转发,imp=forward_imp = (IMP)_objc_msgForward_impcache全局搜索找到其汇编实现
    STATIC_ENTRY __objc_msgForward_impcache
    // No stret specialization.
    b   __objc_msgForward
    END_ENTRY __objc_msgForward_impcache
    
    ENTRY __objc_msgForward
    adrp    x17, __objc_forward_handler@PAGE
    ldr p17, [x17, __objc_forward_handler@PAGEOFF]
    TailCallFunctionPointer x17
    END_ENTRY __objc_msgForward
    
    • 搜索_objc_forward_handler找到其实现代码
    // 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);
    }
    
    • 这个就是我们平常在方法找不到的时候报的unrecognized selector错误

    resolveInstanceMethod动态方法决议

    • oc在调用方法时发现方法没有实现,会再给我们3次机会,第一次就是动态方法决议,后面两次机会就是后面的消息转发的快速转发和慢速转发
    • 实现resolveInstanceMethod方法
    + (BOOL)resolveInstanceMethod:(SEL)sel
    {
        NSLog(@"%@来了",NSStringFromSelector(sel));
    //    if (sel == @selector(run)) {
    //
    //        return class_addMethod(self, sel, class_getMethodImplementation(self, @selector(test1)), "v@:");;
    //    }
        return [super resolveInstanceMethod:sel];
    }
    
    • 发现打印了2次run来了,通过断点调试打印函数调用堆栈,发现第一次调用堆栈如下
    * thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 12.1
      * frame #0: 0x00000001003b8191 libobjc.A.dylib`resolveInstanceMethod(inst=0x000000010112dfc0, sel="run", cls=Person) at objc-runtime-new.mm:6000:12
        frame #1: 0x00000001003a3b10 libobjc.A.dylib`resolveMethod_locked(inst=0x000000010112dfc0, sel="run", cls=Person, behavior=1) at objc-runtime-new.mm:6043:9
        frame #2: 0x00000001003a3423 libobjc.A.dylib`::lookUpImpOrForward(inst=0x000000010112dfc0, sel="run", cls=Person, behavior=1) at objc-runtime-new.mm:6192:16
        frame #3: 0x000000010037ea99 libobjc.A.dylib`_objc_msgSend_uncached at objc-msg-x86_64.s:1101
        frame #4: 0x00000001000018f3 Test`main(argc=1, argv=0x00007ffeefbff588) at main.mm:195:9 [opt]
        frame #5: 0x00007fff68db27fd libdyld.dylib`start + 1
        frame #6: 0x00007fff68db27fd libdyld.dylib`start + 1
    
    • 第二次调用堆栈
    * thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 12.1
      * frame #0: 0x00000001003b8191 libobjc.A.dylib`resolveInstanceMethod(inst=0x0000000000000000, sel="run", cls=Person) at objc-runtime-new.mm:6000:12
        frame #1: 0x00000001003a3b10 libobjc.A.dylib`resolveMethod_locked(inst=0x0000000000000000, sel="run", cls=Person, behavior=0) at objc-runtime-new.mm:6043:9
        frame #2: 0x00000001003a3423 libobjc.A.dylib`::lookUpImpOrForward(inst=0x0000000000000000, sel="run", cls=Person, behavior=0) at objc-runtime-new.mm:6192:16
        frame #3: 0x000000010037d559 libobjc.A.dylib`::class_getInstanceMethod(cls=Person, sel="run") at objc-runtime-new.mm:5922:5
        frame #4: 0x00007fff3174f7d6 CoreFoundation`__methodDescriptionForSelector + 282
        frame #5: 0x00007fff3176b5a0 CoreFoundation`-[NSObject(NSObject) methodSignatureForSelector:] + 38
        frame #6: 0x00007fff317376e4 CoreFoundation`___forwarding___ + 408
        frame #7: 0x00007fff317374b8 CoreFoundation`__forwarding_prep_0___ + 120
        frame #8: 0x00000001000018f3 Test`main(argc=1, argv=0x00007ffeefbff588) at main.mm:195:9 [opt]
        frame #9: 0x00007fff68db27fd libdyld.dylib`start + 1
        frame #10: 0x00007fff68db27fd libdyld.dylib`start + 1
    
    • 在第一次动态方法解析之后,会进行消息转发,消息转发又分为快速转发forwardingTargetForSelector和慢速转发methodSignatureForSelector``forwardInvocation如果快速转发没有处理,就会进入慢速转发的methodSignatureForSelector,处理完之后会再次进入lookUpImpOrForward

    消息转发

    • 上文提到的动态方法决议没有处理之后,报错如下
    *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Person run]: unrecognized selector sent to instance 0x1006584a0'
    *** First throw call stack:
    (
        0   CoreFoundation                      0x00007fff317d38ab __exceptionPreprocess + 250
        1   libobjc.A.dylib                     0x000000010038e050 objc_exception_throw + 48
        2   CoreFoundation                      0x00007fff31852b61 -[NSObject(NSObject) __retain_OA] + 0
        3   CoreFoundation                      0x00007fff31737adf ___forwarding___ + 1427
        4   CoreFoundation                      0x00007fff317374b8 _CF_forwarding_prep_0 + 120
        5   Test                                0x00000001000018f3 main + 67
        6   libdyld.dylib                       0x00007fff68db27fd start + 1
    )
    libc++abi.dylib: terminating with uncaught exception of type NSException
    
    • 发现报错的位置在CoreFoundation,并不在我们当前的libobjc源码环境下,我们该怎么继续往下分析呢
      • 使用instrumentObjcMessageSends方法打印日志
      • 通过反编译工具(hopper/IDA)查看CoreFoundation

    instrumentObjcMessageSends方法的使用

    • 首先通过lookUpImpOrForward->log_and_fill_cache->logMessageSend找到logMessageSend
    if (slowpath(objcMsgLogEnabled && implementer)) {
            bool cacheIt = logMessageSend(implementer->isMetaClass(), 
                                          cls->nameForLogging(),
                                          implementer->nameForLogging(), 
                                          sel);
            if (!cacheIt) return;
        }
    
    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;
    }
    
    • 执行logMessageSend需要满足objcMsgLogEnabled为YES,于是就有下面代码打印方法执行的日志,需要注意的是使用ios项目并不会在/tmp/文件夹下生成msgSends-%d的日志文件
      image.png
    • 找到对应的msgSends-xx文件
    + XQPerson NSObject resolveInstanceMethod:
    + XQPerson NSObject resolveInstanceMethod:
    - XQPerson NSObject forwardingTargetForSelector:
    - XQPerson NSObject forwardingTargetForSelector:
    - XQPerson NSObject methodSignatureForSelector:
    - XQPerson NSObject methodSignatureForSelector:
    - XQPerson NSObject class
    + XQPerson NSObject resolveInstanceMethod:
    + XQPerson NSObject resolveInstanceMethod:
    - XQPerson NSObject doesNotRecognizeSelector:
    - XQPerson NSObject doesNotRecognizeSelector:
    - XQPerson NSObject class
    ......
    

    通过hopper分析CoreFoundation

    • 在lldb调试环境下输入image list,搜索CoreFoundation找到CoreFoundation的路径,将之放到hopper中分析

    • 搜索_forwarding_prep_0选中伪代码,可以看到实现如下

      image.png
    • 双击进到___forwarding___方法

      image.png
    • 找到了forwardingTargetForSelector:然后跳转到loc_6459b

      image.png
    • 做了僵尸对象处理如果methodSignatureForSelector:没有实现跳转到loc_64970

      image.png
    • 执行doesNotRecognizeSelector:报方法未找到错误

    消息转发的继续处理

    • forwardingTargetForSelector消息快速转发,直接返回一个能够处理消息的对象
    - (id)forwardingTargetForSelector:(SEL)aSelector
    {
        return [XQStudent alloc];
    }
    
    • 如果消息快速转发没有处理就会进入到消息慢速转发methodSignatureForSelector,该方法返回一个方法签名,通过官方文档找到和这个方法有关的两个方法forwardInvocation:instanceMethodSignatureForSelector:,forwardInvocation:是对invocation的进一步处理,通过上面分析指导,在这之后会再进行一次动态方法解析
    void doSomething() {
        NSLog(@"%s",__func__);
    }
    
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
    {
        NSMethodSignature *sign = [NSMethodSignature signatureWithObjCTypes:"v@:"];
        return sign;
    //    [NSMethodSignature methodSignatureForSelector:@selector(doSomething)];
    }
    
    - (void)forwardInvocation:(NSInvocation *)anInvocation
    {
    //    anInvocation.selector = @selector(doSomething);
        doSomething();
    //    [anInvocation invoke];
    }
    
    • 由此可以得到一张关于消息转发处理的流程图


      消息转发.png

    总结

    • 整个消息的调用流程
      1,cache的快速查找
      2,如果没找到,则在类的方法列表中查找,如果还是没找到,则去父类链的缓存和方法列表中查找
      3,如果还没找到,第一次补救机会就是尝试一次动态方法决议,即重写resolveInstanceMethod/resolveClassMethod 方法
      4,如果动态方法决议还是没有找到,则进行消息转发,消息转发中有两次补救机会:快速转发+慢速转发
      5,如果转发之后也没有,则程序直接报错unrecognized selector sent to instance

    相关文章

      网友评论

          本文标题:objc_msgSend调用流程

          本文链接:https://www.haomeiwen.com/subject/fssoyktx.html