iOS消息发送流程

上一篇我们讲到从objc_msgSend发送消息进入到了汇编,然后通过CacheLookup---> CheckMiss---> __objc_msgSend_uncached---> MethodTableLookup---> __class_lookupMethodAndLoadCache3,但是我们在源代码中却找不到该方法,那就通过allways show来查找系统调用流程:

一、通过汇编找到方法的调用流程

  • 1.Debug ---> Debug workflow ----> allways show,打开allways show

  • 2.在调用方法那行打上断点

iOS消息发送流程_第1张图片
image.png
  • 3.然后我们就会跳到该方法的汇编流程里了
iOS消息发送流程_第2张图片
image.png
  • 4、从上到下,我们会发现调用该方方法前后的调用顺序,因为我查看的是by_eat5111方法,看到了该方法名,并且下面有个objc_msgSend,我们就能得知调用方法时候会调用objc_msgSend,我们在该方法这行加上一个断点,然后跳到这个断点地方,再按住control 点击step In进入到该方法的调用流程中去:
iOS消息发送流程_第3张图片
image.png
  • 5.我们会进入到objc_msgSend的流程,
iOS消息发送流程_第4张图片
image.png
  • 6.往下翻,我们看到很熟悉的_objc_msgSend_uncached方法,
iOS消息发送流程_第5张图片
image.png

然后依然加个断点,按住control点击step in进入到_objc_msgSend_uncached方法里

  • 7.然后我们发现在该函数里_class_lookupMethodAndLoadCache3的c++函数,并且指示了在objc-runtime-new.mm文件的4846行


    iOS消息发送流程_第6张图片
    image.png
  • 8.我们到objc-runtime-new.mm文件的4846行查看,确实有这个函数,这样我们就开始了C++、C函数的查找流程


    iOS消息发送流程_第7张图片
    image.png

其实不管是C++和C语言函数最终都会编译成机器可以识别的语言,也就是汇编,我们可以通过上述方法一层层查看,可以看到整个的方法调用逻辑

1、这次我们通过allways show方式看到了OC方法调用顺序是objc_msgSend -- > _objc_msgSend_uncached -- > _class_lookupMethodAndLoadCache3 ...
2、跟我们从源码分析有些不同,缺少了CacheLookup 、CheckMiss 、MethodTableLookup,猜测是编译器优化了一些调用流程,只编译那些方法会走的流程,不走的流程直接舍弃了
3、假如我们不知道一个方法的调用逻辑,我们可以通过allways show这种方式一步一步去查找,很方便了就能看出一个方法的调用流程

二、方法查找流程

我们都知道当调用了未实现的方法时候会导致程序崩溃,那我们就分析一下方法调用流程来了解下为啥会崩溃。

上面我们分析了从objc_msgSend到_class_lookupMethodAndLoadCache3的查找步骤,现在我们再通过源码部分查看方法的查找流程,在方法关键位置加了注释

  • 1、方法查找并缓存的总函数:_class_lookupMethodAndLoadCache3,我们发现里面掉的是
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
    /*
     cls:如果是实例方法那就是类,如果是类方法那就是元类
     sel:方法名
     obj:方法调用者
     */
    return lookUpImpOrForward(cls, sel, obj, 
                              YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}
  • 2、查找方法指针或消息转发函数:lookUpImpOrForward
    • a、我们发现进入后会先从缓存再找一次,找到的话就直接返回了,

    • b、然后对类是否加载完进行判断,如果没有加载完就再加载一遍,

    • c、然后设置了两个局部域,
      (1)第一个是先从自己的方法列表里面找,如果没找到在执行第二个域
      (2)第二个域会遍历父类,每遍历一次就会先从父类的缓存中找,父类的缓存没有的话再从父类的方法列表中找
      (3)执行两个域过程中,如果找到了就缓存在自己的cache_t里面,假如自己的列表和父类的列表都没找到的话话,就开始了消息转发函数_class_resolveMethod

    • e、_class_resolveMethod函数调用完成后会再走一下c流程,如果没有的话,就会调用_objc_msgForward_impcache函数,然后返回imp

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
//如果需要从缓存里面查找,那需要先从缓存里面找,现在我们这边传进来的是NO,也就不走这一步,但是消息转发后传进来的是YES,需要走下
    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.
    //主要是对class的判断,lock是为了防止多线程操作
    runtimeLock.lock();
    checkIsKnownClass(cls);

//为查找方法做准备条件,判断类有没有加载好,如果没有加载好,那就先加载一下类信息,准备好父类、元类
    if (!cls->isRealized()) {
        realizeClass(cls);
    }
    //确定类已经加载完成,
    if (initialize  &&  !cls->isInitialized()) {
        runtimeLock.unlock();
        _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.
    //判断该方法从缓存里面查找一遍
    imp = cache_getImp(cls, sel);
    //找到了直接跳到done
    if (imp) goto done;
    //大括号意思是为了形成局部域,避免局部变量命名重复
    // Try this class's method lists.
    {
        //先在自己类里面找方法
        Method meth = getMethodNoSuper_nolock(cls, sel);
        if (meth) {
            //如果找到了就进入到了缓存逻辑cache_fill
            log_and_fill_cache(cls, meth->imp, sel, inst, cls);
            imp = meth->imp;
            goto done;//直接跳到done这个点
        }
    }

    // Try superclass caches and method lists.
    //自己b类中找不到的话再到父类方法列表中找
    {
        //这个不懂
        unsigned attempts = unreasonableClassCount();
        //遍历f类f的父类们,
        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) {
                ///如果不是_objc_msgForward_impcache这个方法,为啥???
                if (imp != (IMP)_objc_msgForward_impcache) {
                    // Found the method in a superclass. Cache it in this class.
                    ///就把这个方法f缓存在当前类的缓存中
                    log_and_fill_cache(cls, imp, sel, inst, curClass);
                    goto done;
                }
                else {///如果是_objc_msgForward_impcache方法,就退出,不需要遍历了,因为_objc_msgForward_impcache这个方法就是未实现时候转发用的
                    // 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.unlock();
        /*
         实例方法解析:resolveInstanceMethod
         类方法解析:resolveClassMethod
         */
        _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.
        //设置为NO,防止因为消息动态解析导致死循环
        triedResolver = YES;
        goto retry;
    }

    // No implementation found, and method resolver didn't help. 
    // Use forwarding.
    //如果第一次转发还是没用的话,就取出_objc_msgForward_impcache这个方法指针
    imp = (IMP)_objc_msgForward_impcache;
    //缓存起来将指针返回回去执行
    cache_fill(cls, sel, imp, inst);

 done:
    runtimeLock.unlock();

    return imp;
}
  • 3、消息动态解析函数:_class_resolveMethod
    • a、首先会判断cls是不是元类,如果cls不是元类的话,说明调用的是实例方法,那就就会调用_class_resolveInstanceMethod函数,

    • b、如果是元类的话,说明调用的是类方法,那么就会调用_class_resolveClassMethod函数,并且调用完后会再次查找一下sel的指针,找到了就会返回,如果还是找不到的话会调用_class_resolveInstanceMethod函数,这是为什么呢:
      (1)通过isa指向我们知道类的元类的父类都是NSObject的元类,而NSObject元类的父类是NSObject类,
      (2)而类里存放的都是对象方法,并且方法名是不分类方法和对象方法的
      (3)所以根据继承链关系,在类方法的查找过程中最终会查找到NSObject类中,所以在消息动态决议中也需要调用一下_class_resolveInstanceMethod去动态决议一下(有点牵强哈)

    • c、_class_resolveInstanceMethod和_class_resolveClassMethod函数调用逻辑很类似,只是一个是解析类方法,一个是解析实例方法:

      (1)首先判断类有没有实现SEL_resolveInstanceMethod方法,其实也就是+ (BOOL)resolveInstanceMethod:(SEL)sel或者+ (BOOL)resolveClassMethod:(SEL)sel方法,我们通过在源码搜索可以看到在NSObject类中已经都实现了,并且都是一个空方法,并且返回NO,
      (2)然后会通过objc_msgSend函数发送SEL_resolveInstanceMethod消息,系统调用起resolveInstanceMethod方法,发送完成后,系统会再查找一下sel方法,然后回到主流程重新走一下方法查找逻辑

      • d、由上我们可以得出动态决议的所有方法最终都会转发到重写的resolveInstanceMethod方法里,我们可以集中在这里做处理,例如防崩溃措施等等,但是不建议放在这里,因为会导致耦合太大
void _class_resolveMethod(Class cls, SEL sel, id inst)
{
    if (! cls->isMetaClass()) {//判断类不是元类,那sel就是实例方法,那就先转发resolveInstanceMethod方法,判断有没有实现resolveInstanceMethod,没实现就不做处理
        // try [cls resolveInstanceMethod:sel]

        _class_resolveInstanceMethod(cls, sel, inst);
    } 
    else {//如果是元类,那sel就是类方法
        // try [nonMetaClass resolveClassMethod:sel]
        // and [cls resolveInstanceMethod:sel]
    //先转发resolveClassMethod,会先查找下resolveClassMethod,如果没实现就不做处理
        _class_resolveClassMethod(cls, sel, inst);
        //再次查找下方法,如果没有的话,就再转发一下resolveInstanceMethod方法
        if (!lookUpImpOrNil(cls, sel, inst, 
                            NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
        {
            _class_resolveInstanceMethod(cls, sel, inst);
        }
    }
}
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;
    }
    //发送SEL_resolveInstanceMethod消息,
    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
    //再次查找类中是否sel方法,因为resolveInstanceMethod方法执行后可能动态进行添加了,resolver是不要进行消息转发了
    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));
        }
    }
}
static void _class_resolveClassMethod(Class cls, SEL sel, id inst)
{
    assert(cls->isMetaClass());
  //查找下类是否实现了resolveClassMethod方法,NSObject类已经实现了
    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;
//记住,此处是向元类发送resolveClassMethod消息,也就是调用resolveClassMethod方法
    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));
        }
    }
}
  • 4、当转发也完成后依然找不到IMP时候,系统就会调用__objc_msgForward_impcache,
    • a、通过全局查找,我们发现只在汇编有进入该函数的入口,最终我们会发现调用了__objc_forward_handler函数,
    • b、然后在查找_objc_forward_handler,当我们把前面去掉一个会找到,然后发现里面内容就是我经常因为方法不存在而导致崩溃所提示的信息
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 __objc_msgForward_stret
    jmp __objc_msgForward

    END_ENTRY __objc_msgForward_impcache
    
    
    ENTRY __objc_msgForward
    // Non-stret version

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

    END_ENTRY __objc_msgForward
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;

总结:

  • 1、先确定调用方法的类已经都加载完毕,如果没加载完毕的话进行加载

  • 2、查找时候先加锁,避免多线程导致出现问题,然后在从缓存里面取一遍,如果取到IMP直接返回IMP

  • 3、先从自己的方法列表中查找,如果找到就把找到的imp缓存到buckets中,然后返回imp

  • 4、再遍历父类,遍历父类时候,先从父类缓存中取,然后再从父类的方法列表中去,如果取到了会把该方法缓存在本类的buckets中,并且如果找到的方法是_objc_msgForward_impcache,会过滤掉并且终止查找,

  • 5、如果本类和父类中都没找到那就开始走消息转发流程,实例方法会转发+(BOOL) resolveInstanceMethod:(SEL)sel;类方法会转发+(BOOL) resolveClassMethod:(SEL)sel,并且类方法转发完后会再次走查找流程,如果还没找到的话会走一下实例方法转发流程;转发逻辑完成后,会再次走一下方法查找逻辑

  • 6、如果第一次转发依然后还没找到IMP,那么就会返回_objc_msgForward_impcache方法指针

  • 7、通过全局搜索_objc_msgForward_impcache,最后只在汇编代码中找到该函数,然后会发现最后调用的是_objc_forward_handler函数,最后在_objc_forward_handler实现里面找到了经典的因为方法没实现导致报错的错误信息

源码:

试验

既然知道当没调用了不存在的方法时候会转发resolveClassMethod或resolveInstanceMethod方法,那我们在类中实现一下,然后做一下试验:

代码
-(void)sayHello1{
    
    NSLog(@"消息转发过来的实例方法");
}

+(void)sayHello2{

    NSLog(@"消息转发过来的类方法");
}

+(BOOL)resolveInstanceMethod:(SEL)sel{

    NSLog(@"%s",__func__);

    IMP sayHIMP = class_getMethodImplementation(self.class, @selector(sayHello1));

    Method sayHMethod = class_getInstanceMethod(self.class, @selector(sayHello1));

    const char *sayHType = method_getTypeEncoding(sayHMethod);

    return class_addMethod(self.class, sel, sayHIMP, sayHType);
}

+(BOOL)resolveClassMethod:(SEL)sel{

    NSLog(@"%s",__func__);

    IMP sayHIMP = class_getMethodImplementation(self, @selector(sayHello2));

    Method sayHMethod = class_getClassMethod(self, @selector(sayHello2));
    
    const char *sayHType = method_getTypeEncoding(sayHMethod);

    return class_addMethod(self, sel, sayHIMP, sayHType);

}

结果

2019-12-29 10:49:52.843118+0800 LGTest[39900:965223] +[BYTestCache_t resolveInstanceMethod:]
2019-12-29 10:49:52.844133+0800 LGTest[39900:965223] 消息转发过来的实例方法

我们发现调用不存在的实例方法时候,动态给sel添加IMP后确实没有崩溃,说明之前的验证正确

但是当我们调用不存在的类方法时候结果:

2019-12-29 10:52:29.232292+0800 LGTest[40044:968519] +[BYTestCache_t resolveClassMethod:]
2019-12-29 10:52:29.232951+0800 LGTest[40044:968519] +[BYTestCache_t resolveInstanceMethod:]
2019-12-29 10:52:29.233782+0800 LGTest[40044:968519] +[BYTestCache_t by_run1]: unrecognized selector sent to class 0x100002888
2019-12-29 10:52:29.239341+0800 LGTest[40044:968519] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '+[BYTestCache_t by_run1]: unrecognized selector sent to class 0x100002888'
*** First throw call stack:
(
    0   CoreFoundation                      0x00007fff51481acd __exceptionPreprocess + 256
    1   libobjc.A.dylib                     0x000000010035546a objc_exception_throw + 42
    2   CoreFoundation                      0x00007fff514fb826 __CFExceptionProem + 0
    3   CoreFoundation                      0x00007fff5142393f ___forwarding___ + 1485
    4   CoreFoundation                      0x00007fff514232e8 _CF_forwarding_prep_0 + 120
    5   LGTest                              0x00000001000018d4 main + 116
    6   libdyld.dylib                       0x00007fff7d3523d5 start + 1
    7   ???                                 0x0000000000000001 0x0 + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException

在调用了一次resolveClassMethod后,又调用了一次resolveInstanceMethod后,然后崩溃了,是因为类方法是存储在元类中,而类的self.class是返回的是自己不是元类,所以需要改成object_getClass(self)才行

拓展问题:
1、假如调用了不存在的实例方法并且在resolveInstanceMethod直接返回NO,我们会发现resolveInstanceMethod方法会被调用两回再崩溃
2、当我们把实例方法替换成类方法的指针时候,或者将类方法替换成实例方法指针时候,如果不在resolveInstanceMethod里面做相应的要替换方法判断,就会导致死循环,如果做了判断,老方法会调用一次resolveInstanceMethod,新方法也会调用一次resolveInstanceMethod

另外附上一张经典的消息转发流程图,一般人不告诉他:

iOS消息发送流程_第8张图片
消息转发流程.png

你可能感兴趣的:(iOS消息发送流程)