阅读 1190

iOS 消息查找流程

1、前言

上一篇 从汇编探索objc_msgSend 遗留了一个 __class_lookupMethodAndLoadCache3,接下来就是对这个方法进行分析,也就会来到了消息查找流程,消息查找流程分为快速和慢速,快速查找已经在 objc_msgSend 找过了,找不到就会进入慢速查找,慢速查找流程会在 __class_lookupMethodAndLoadCache3 方法进行,但是我先不直接分析,先来点铺垫,想看源码分析的可以跳过第二步。

2、isa 走位图

我们先创建 StudentPerson 两个对象,Student -> Person -> NSObject(->代表继承),然后在两个对象以及 NSObject 分类中各自添加一个实例方法和类方法。

#pragma clang diagnostic push
// 让编译器忽略错误
#pragma clang diagnostic ignored "-Wundeclared-selector"

        Student *student = [[Student alloc] init];
        // 对象方法测试
        // 1.对象的实例方法 - 自己有
        [student studentSayHello];
        // 2.对象的实例方法 - 自己没有 - 找老爸的
        [student personSayNB];
        // 3.对象的实例方法 - 自己没有 - 老爸没有 - 找老爸的老爸 - NSObject
        [student nsobjectSayMaster];
        // 4.对象的实例方法 - 自己没有 - 老爸没有 - 找老爸的老爸 -> NSObject 也没有 - 奔溃
        // [student performSelector:@selector(saySomething)];
        
        // 类方法测试
        // 5.类方法 - 自己有
        [Student studentSayObjc];
        // 6.类方法 - 自己没有 - 老爸有
        [Student personSayHappay];
        // 7.类方法 - 自己没有 - 找老爸的老爸 - NSObject
        [Student nsobjectSayEasy];
        // 8.类方法- 自己没有 - 老爸没有 - 找老爸的老爸 -> NSObject 也没有 - 奔溃
        // [LGStudent performSelector:@selector(sayLove)];
        // 9.类方法- 自己没有 - 老爸没有 - 找老爸的老爸 -> NSObject 也没有 - 但是有对象方法,能走不会奔溃
        [Student nsobjectSayEasy];

#pragma clang diagnostic pop
复制代码

上面几个方法的调用结果我已经写到方法上面了,但是第9点,为什么类调用 NSObject 的对象方法能够成功,这就需要一个很牛皮的图了,isa走位图,下面奉上,由于类方法会保存在元类的对象方法列表里,所以类调用类方法时,会去元类的对象方法列表里面找,没找到再去根元类里找,根元类的父类是 NSObject,所以会找到 NSObject 里,所以 Student 最终会在 NSObject 对象方法列表里找到 nsobjectSayEasy 进行调用。

isa流程图

3、消息查找流程

1.__class_lookupMethodAndLoadCache3 方法分析

当汇编的 objc_msgSend 在缓存没有找到方法时,最终会走到这个方法进行慢速查找方法。

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 方法查找流程
IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                       bool initialize, bool cache, bool resolver)
{
    IMP imp = nil;
    bool triedResolver = NO;
    runtimeLock.assertUnlocked();

    // 根据外界传来的值进行判断,这里为 NO,不走
    if (cache) {
        imp = cache_getImp(cls, sel);
        if (imp) return imp;
    }
    // 防止多线程访问两个方法出现返回imp错误
    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();

    // 类初始化后再对缓存进行一次查找,个人认为苹果爸爸还是很严谨的
    imp = cache_getImp(cls, sel);
    if (imp) goto done;
   
    {
        // 在当前类中找方法,里面会通过二分查找节省时间,有兴趣的可以自己去看看,在 search_method_list 这个方法里面
        Method meth = getMethodNoSuper_nolock(cls, sel);
        if (meth) {
            // 找到了进行缓存一份
            log_and_fill_cache(cls, meth->imp, sel, inst, cls);
            imp = meth->imp;
            goto done;
        }
    }

    {
        // 当本类找不到的时候,就会去父类缓存查找,如果没有在递归查找
        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.");
            }
            
            // 看看父类的缓存里面是否存在该方法
            imp = cache_getImp(curClass, sel);
            if (imp) {
                if (imp != (IMP)_objc_msgForward_impcache) {
                    // 找到后进行缓存
                    log_and_fill_cache(cls, imp, sel, inst, curClass);
                    goto done;
                }
                else {
                    break;
                }
            }
            
            // 没有缓存就去父类的方法列表慢速查找
            Method meth = getMethodNoSuper_nolock(curClass, sel);
            if (meth) {
                log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
                imp = meth->imp;
                goto done;
            }
        }
    }

    // 如果本类和父类以及父父类都没找到,就会进行动态方法决议
    if (resolver  &&  !triedResolver) {
        runtimeLock.unlock();
        _class_resolveMethod(cls, sel, inst);
        runtimeLock.lock();
        // 此处代码只会走一次,因为 triedResolver 会设置为 YES
        triedResolver = YES;
        goto retry;
    }

    // No implementation found, and method resolver didn’t help. 
    // 如果本类及父类都没有找到,动态方法决议也没有帮助,就会调用 _objc_msgForward_impcache 方法
    imp = (IMP)_objc_msgForward_impcache;
    cache_fill(cls, sel, imp, inst);

 done:
    runtimeLock.unlock();
    return imp;
}
复制代码
  • 总结:上述整体流程可以发现,方法的查找流程先会去本类找,再去父类以及父父类里找,如果都没有找到,就会进行 _class_resolveMethod 动态方法决议(动态方法决议下篇博客会分析到),如果上述都没有帮助,则会调用 _objc_msgForward_impcache 方法,这个方法又是做什么的呢?马上介绍。
3._objc_msgForward_impcache 探索

通过全局搜索,一步步查找来到 objc-msg-arm64.s 汇编文件中找到下面汇编代码,之后会调用 __objc_forward_handler

    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
复制代码

由于这个函数是 C 函数,我们去掉前面一个 _ 进行全局搜索_objc_forward_handler,就能找到没有实现方法所造成的崩溃原因了,哈哈哈哈,是不是很熟悉,unrecognized selector sent to instance 这个崩溃原因经常遇到,终于找到底层源码在哪了。

__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;
复制代码

4、总结

  • 1.当在 objc_msgSend 缓存中没有找到方法,就会来到 class_lookupMethodAndLoadCache3 进行慢速查找流程。
  • 2.在 lookUpImpOrForward 里面会先去本类当中查找方法 getMethodNoSuper_nolock,本类没有找到就会去递归的去父类当中查找。
  • 3.如果本类和父类都没有找到,就会进行动态方法决议 _class_resolveMethod,这是苹果爸爸给我们的最后一次机会。
  • 4.动态方法我们还不处理,最后就会走到 _objc_forward_handler,然后崩溃报错 selector sent to instance
文章分类
iOS
文章标签