7、iOS底层分析 - 消息查找流程

方法的查找流程

  1. 对象方法测试
    1. 对象的实例方法 - 自己有
    2. 对象的实例方法 – 自己没有 – 找父类的
    3. 对象的实例方法 – 自己没有 – 父类没有 – 找父类的父类 – NSObject
    4. 对象的实例方法 – 自己没有 – 父类没有 – 找父类的父类 – NSObject也没有 – 崩溃
    5. 总结: SelfClass -> SuperClass ->...->NSObject -> nil
  2. 类方法测试
    1. 类方法 – 自己有
    2. 类方法 – 自己没有 – 父类有
    3. 类方法 – 自己没有 – 父类没有 – 父类的父类 – NSObject
    4. 类方法 – 自己没有 – 父类没有 – 父类的父类 – NSObject也没有 – 崩溃
    5. 类方法 – 自己没有 – 父类没有 – 父类的父类 – NSObject也没有 – 但是有对象方法,调用这个对象方法
    6. 总结:SelfMetaClass -> SuperMetaClass -> ... -> NSObjectMetaClass-> NSObject -> nil
    7. 这个有个点,类方法在元类中是以实例(对象)方法的形式存在的,所以例如 LGPerson 调用 一个NSObject的实例方法,这样也是可以的。
  3. 动态方法决议
  4. 消息转发
  5. 如果找不到,报错崩溃
    1. 无法识别的选择器发送给实例
2020-01-20 21:03:41.414708+0800 LGTest[1213:36305] 
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', 
reason: '-[LGPerson sayHello]: unrecognized selector sent to instance 0x101848d30'

流程分析

objc_msgSend 先到缓存进行快速查找,如果找不到,则进行慢速常规查找

快速查找的流程就是前面分析的消息发送流程,那么现在来看 _class_lookupMethodAndLoadCache3 普通查找流程

objc_calss_old.mm 中
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{        
    return lookUpImpOrForward(cls, sel, obj, 
                              YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}

lookUpImpOrForward分析

这个方法很重要,一定要记住

  • 首先进行一系列准备,保证当对象方法或者类方法没有找到时,能够有父类或者元类能够去让我们去继续去查找,一系列递归

慢速查找,通过 obcj_msgSend 快速查找没有找到,需要到类里面进行慢速查找

  • IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                           bool initialize, bool cache, bool resolver)
    {
        IMP imp = nil;
        bool triedResolver = NO;
    
        runtimeLock.assertUnlocked();
    
        传进来的cache 是NO 所以不用看这一部分
        接下来是判断是否是非法的类,判断也不用看
    
        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);
        if (imp) goto done;
    
    下边加括号的目的是 实现局部作用域,两个作用域内的变量名重复不会冲突
        // Try this class's method lists.
        {
            去当前类的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;
            }
        }
    
        // Try superclass caches and method lists.
        {
            当前类的父类中的查找方法
            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.");
                }
                
                // Superclass cache.
                imp = cache_getImp(curClass, sel);
                if (imp) {
                    if (imp != (IMP)_objc_msgForward_impcache) {
                        // Found the method in a superclass. Cache it in this class.
                        log_and_fill_cache(cls, imp, sel, inst, curClass);
                        goto done;
                    }
                    else {
                        // 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();
            _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.
            triedResolver = YES;
            goto retry;
        }
    
        // No implementation found, and method resolver didn't help. 
        // Use forwarding.
    
        imp = (IMP)_objc_msgForward_impcache;
        cache_fill(cls, sel, imp, inst);
    
     done:
        runtimeLock.unlock();
    
        return imp;
    }

先准备类(包括父类和元类),然后到类或父类的 method_list 查找方法。先判断一下当前imp有没有被缓存,如果存在缓存那么直接调用即可(这个时候可能有缓存进来)

之后通过 getMethodNoSuper_nolock 进行方法列表查找

static Class realizeClass(Class cls)
{
    runtimeLock.assertLocked();

    const class_ro_t *ro;
    class_rw_t *rw;
    Class supercls;
    Class metacls;
    bool isMeta;
   。。。。。。
    
    supercls = realizeClass(remapClass(cls->superclass));  递归实现父类
    metacls = realizeClass(remapClass(cls->ISA()));        递归实现元类

#if SUPPORT_NONPOINTER_ISA
。。。。。。
// SUPPORT_NONPOINTER_ISA
#endif
    。。。。。
    // Attach categories
    methodizeClass(cls);

    return cls;
}

getMethodNoSuper_nolock

getMethodNoSuper_nolock 方法内部,通过对  methods  列表进行遍历,找到当前sel对应的method,如果找不到则返回nil

static method_t *
getMethodNoSuper_nolock(Class cls, SEL sel)
{
    runtimeLock.assertLocked();

    assert(cls->isRealized());
    // fixme nil cls? 
    // fixme nil sel?

    for (auto mlists = cls->data()->methods.beginLists(), 
              end = cls->data()->methods.endLists(); 
         mlists != end;
         ++mlists)
    {
        method_t *m = search_method_list(*mlists, sel);
        if (m) return m;
    }

    return nil;
}

在  search_method_list  过程之后,通过  findMethodInSortedMethodList(sel, mlist)  进行二分查找,保证能够快速寻找到目标方法。

具体的二分查找代码

// 关键二分查找代码
for (count = list->count; count != 0; count >>= 1) {
        probe = base + (count >> 1);
        
        uintptr_t probeValue = (uintptr_t)probe->name;
        
        if (keyValue == probeValue) {
            // `probe` is a match.
            // Rewind looking for the *first* occurrence of this value.
            // This is required for correct category overrides.
            while (probe > first && keyValue == (uintptr_t)probe[-1].name) {
                probe--;
            }
            return (method_t *)probe;
        }
        
        if (keyValue > probeValue) {
            base = probe + 1;
            count--;
        }
    }

找到 method_t 返回,如果没找到继续下一步,找到后然后 cache_fill 去缓存

方法被找到,进入 log_and_fill_cache 方法,内部调用 cache_fill 进入缓存流程,之后进入goto done

继续查找的流程

1)、如果自己没有,去查找父类。

2)、先看父类缓存有没有 imp = cache_getImp(cls, sel)

没有再到父类的方法列表去查 Method meth = getMethodNoSuper_nolock(cls, sel)

3)、如果父类还没找到,类方法会去找父类的元类

class curClass = cls –> superclass (元类的的父类,就是父类的元类)

同上查找

4)、如果父类的元类中也没有找到,就去NSObject中找

static void
log_and_fill_cache(Class cls, IMP imp, SEL sel, id receiver, Class implementer)
{
#if SUPPORT_MESSAGE_LOGGING
    if (objcMsgLogEnabled) {
        bool cacheIt = logMessageSend(implementer->isMetaClass(), 
                                      cls->nameForLogging(),
                                      implementer->nameForLogging(), 
                                      sel);
        if (!cacheIt) return;
    }
#endif
    cache_fill (cls, sel, imp, receiver);
}

如果以上流程方法还是没有找到,那么首先会进行动态方法解析  _class_resolveMethod, 在这个过程中,系统会调用一次已经存在的事先定义好的两个类方法,提供一次容错的机会。如果不想让程序因为查找不到方法就奔溃可以在这里进行处理,可以打印错误提示等。

// objc源码
_class_resolveInstanceMethod(cls, sel, inst)
_class_resolveClassMethod(cls, sel, inst)

// NSObject内部方法
+ (BOOL)resolveClassMethod:(SEL)sel OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
+ (BOOL)resolveInstanceMethod:(SEL)sel OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

如果系统给的这两个方法还是没有有做任何处理,那么就会进入消息转发阶段,消息转发阶段全局搜索一下该方法。在其汇编方法内部,调用了__objc_msgForward

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(汇编的在c++中要去掉一个下划线),找到了 objc_defaultForwardHandler  方法的具体实现 

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;

如果什么都没处理,也找不到方法就会崩溃报错

2020-01-20 21:03:41.414708+0800 LGTest[1213:36305] 
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', 
reason: '-[LGPerson sayHello]: unrecognized selector sent to instance 0x101848d30'

总结

  • 方法查找,先是通过 objc_msgSend 的快速缓存查找,之后再通过对类以及父类的方法列表进行二分法常规查找
  • 最后如果都没有找到需要的方法,那么先进入动态方法解析,之后进入消息的快速转发和常规转发,消息转发处理失败之后则报错 crash

 

引申:

如果方法的查找失败就会崩溃,有时候我们不想让崩溃就可以通过消息转发机制来处理

方法查找流程 分为快速流程  慢速流程

消息转发机制也分为快速流程  慢速流程

发布了83 篇原创文章 · 获赞 12 · 访问量 18万+

猜你喜欢

转载自blog.csdn.net/shengdaVolleyball/article/details/104056265