前提
代码演示的项目和方法的本质中Demo结构预览展示的一样,直接参考。
代码演示方法查找
这个可以用代码来演示:

上面的代码中总结:
自己有直接找到,
自己没有去父类找,有直接找到,
自己没有、父类没有、就去父类的父类去找,直到找到NSObject,都没有找到,会造成崩溃。
比如序号1和序号2,项目中并没有实现saySomething()方法,所以找不到,报错:
unrecognized selector sent to instance/class;报错的后面分析。
那么序号3是什么意思?Student类为什么能直接调用nsobjectA方法(这是个对象方法啊)?并且编译通过不崩溃。继续往下看
查找流程
上边代码的演示有些疑惑的地方,都在图里:红色表示实例方法查找流程,绿色表示类方法查找流程。
实例方法存在类中,类方法存在元类中。调用方法就按照这个图,实例方法在类中依次向父类中查找,找到则已,找不到的话(找到nil为止终止循环),如果没有实现消息转发的话,就会崩溃;类方法在元类中查找同上,但是有一个点需要注意,绿色框的位置,根元类的父类是NSObject,这也是为什么代码演示序号3中,Student类能调用nsobjectA这个实例方法的原因。
分析源码
在方法的本质这篇博客中了解到,方法调用,在底层编译就是objc_msgSend,查找方法先通过汇编到cache中快速查找,如果找不到,就会会调到C语言函数_class_lookupMethodAndLoadCache3开始慢速查找,接下来就开始分析慢速查找的源码逻辑。
_class_lookupMethodAndLoadCache3
/************************************************************************ _class_lookupMethodAndLoadCache.* Method lookup for dispatchers ONLY. OTHER CODE SHOULD USE lookUpImp().* This lookup avoids optimistic cache scan because the dispatcher* already tried that.**********************************************************************/IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls){return lookUpImpOrForward(cls, sel, obj,YES/*initialize*/, NO/*cache*/, YES/*resolver*/);}
_class_lookupMethodAndLoadCache3方法内直接调用的lookUpImpOrForward方法,项目演示看一下这三个参数:
student调用studentA()方法,第一次调用,cache中必然找不到,就需要走到慢速查找的路径来直接在_class_lookupMethodAndLoadCache3方法中打断点追踪。
一目了然:obj就是指针student;sel就是调用的方法studentA;cls就是Student类。
然后继续走进lookUpImpOrForward分析,后边的参数中cache为NO。
lookUpImpOrForward源码
**
我这里直接粘贴上源码,源码上已经写好分析的注释:
/************************************************************************ lookUpImpOrForward.* The standard IMP lookup.* initialize==NO tries to avoid +initialize (but sometimes fails)* cache==NO skips optimistic unlocked lookup (but uses cache elsewhere)* Most callers should use initialize==YES and cache==YES.* inst is an instance of cls or a subclass thereof, or nil if none is known.* If cls is an un-initialized metaclass then a non-nil inst is faster.* May return _objc_msgForward_impcache. IMPs destined for external use* must be converted to _objc_msgForward or _objc_msgForward_stret.* If you don't want forwarding at all, use lookUpImpOrNil() instead.**********************************************************************/IMP lookUpImpOrForward(Class cls, SEL sel, id inst,bool initialize, bool cache, bool resolver){IMP imp = nil;bool triedResolver = NO;runtimeLock.assertUnlocked();// 断言的不用管// Optimistic cache lookupif (cache) { // 如果cache为YES的话,就直接在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.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}// 上边的都是一些为了安全起见的判断和准备,下边开始imp查找的流程retry:runtimeLock.assertLocked();// Try this class's cache.imp = cache_getImp(cls, sel);// 现在缓存中找,找到直接goto doneif (imp) goto done;// Try this class's method lists.// 在本类中的方法列表中查找,找到就goto done{Method meth = getMethodNoSuper_nolock(cls, sel);if (meth) {// 找到这个Method,将这个imp填充到缓存中log_and_fill_cache(cls, meth->imp, sel, inst, cls);imp = meth->imp;goto done;}}// Try superclass caches and method lists.// 如果在本类中查找不到,就开启循环在父类(及父类的父类)中进行查找// 在父类中查找也是优先看缓存,缓存中没有再去method list中查找// 同样找到之后会将这个方法在cls中缓存,注意的是,这个cls是外界传进来的并不是循环中的父类{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.// 在curClass(父类)中的方法列表中查找,找到就goto doneMethod 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.// 没有找到的话,尝试一下_class_resolveMethod处理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;}
方法查找的流程在那个经典的图中已经很清晰,上边的源码,就是逻辑的代码实现。具体的细节可以lldb跟一下源码就可以了。
总结
方法的查找流程分为快速查找和慢速查找,快速查找就是通过汇编在缓存中查找,如果找不到,开始慢速查找,在类及其父类的method list中向上循环查找。
在Demo中,如果都找不到的话,系统就会崩溃,那么这种未找到方法的崩溃,苹果有没有给开发者一些补救的机会呢?下一篇消息转发机制中继续分析。
