我们看这样一个例子:
在NSObject的分类(NSObject+LG)里面定义并实现了sayMaster方法:
@interface NSObject (LG)
- (void)sayMaster;
@end
@implementation NSObject (LG)
- (void)sayMaster{
NSLog(@"%s",__func__);
}
@endNorman是继承自NSObject的一个类,它里面并没有定义任何方法:
@interface Norman : NSObject
@end
@implementation Norman
@end现在我们在外界给Norman类调用sayMaster方法:
[Norman performSelector:@selector(sayMaster)];此时会不会因为找不到对应的方法实现而报崩溃?
答案是不会。
也许你会有疑问,不是实例方法吗?为啥通过类对象也可以调用?且听我慢慢道来。
首先,先到Norman类对象的类(即Norman元类)中去查找有没有对应的方法,没有的话就去Norman元类的父类(即根元类)中去找,再没有的话就去根元类的父类(即NSObject类对象)中去找,此时找到了,所以不会崩溃。
想必大家已经对方法的查找流程有过基本的了解了,所以这个例子大家应该都能理解,接下来我们就从源码层面来分析方法的慢速查找流程。
方法的慢速查找流程分析
在上篇文章方法的查找流程——快速查找中,我们知道,在缓存中没有查找到对应的方法之后,最终会走到_class_lookupMethodAndLoadCache3函数,今天我们就从该函数开始入手研究。
首先看其声明:
/* message dispatcher */
extern IMP _class_lookupMethodAndLoadCache3(id, SEL, Class);这里定义成extern的原因就是为了能够在外界使用该函数。
然后看其实现:
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
return lookUpImpOrForward(cls, sel, obj,
YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}这里就是简单地调用了lookUpImpOrForward函数。这里需要说明以下几点:
接下来查看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 lookup
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.
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);
if (imp) goto done;
// Try this class's method lists.
{
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;
}第53行及之前都是一些准备条件,我们不需要看。我们从第54行开始研究。
第57~59行是去当前类的缓存中去查找,需要说明的是,这里不用走汇编。因为第53行及之前的准备条件已经将缓存给准备好了,这里可以直接获取。
第61~70行是到当前类的方法列表中去查找。这里有以下几点需要说明:
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);
}然后点击cache_fill查看其实现:
void cache_fill(Class cls, SEL sel, IMP imp, id receiver)
{
#if !DEBUG_TASK_THREADS
mutex_locker_t lock(cacheUpdateLock);
cache_fill_nolock(cls, sel, imp, receiver);
#else
_collecting_in_critical();
return;
#endif
}在这里我们看到了一个熟悉的函数:cache_fill_nolock!!!cache_fill_nolock是谁呢?就是我前面在OC类的原理探究(二)——方法的缓存中提到的方法缓存的入口!!!之前我们知道在cache_fill_nolock里面进行扩容、缓存函数等,但是并不知道在哪里会调用cache_fill_nolock函数,现在我们知道cache_fill_nolock函数的调用的地方了,这就串起来了!!
第72行~第108行,是在当前类中没找到对应的方法实现后,到父类当中去查找。跟在当前类中查找的流程一样,也是先到父类缓存中去查找,父类缓存中没找到的话,那就到父类的方法列表中通过二分查找算法去查找。
这里需要说明的是:到父类缓存中去查找不需要走汇编了,因为在第53行之前的准备中就已经将对应的缓存给准备好了,可以直接去缓存中获取。
当找遍当前类及其所有的父类之后,还是没有找到对应的方法实现,那么就会进入下面的消息转发流程。
从第110行往下,都是消息转发流程的相关内容。
我在下面三篇文章中都对消息转发流程做了相关讲述,感兴趣的话可以看一下:
以上。