前面我们已经在runtime之objc_msgSend分析文章中已经对方法快速查找流程进行了分析,并且知道,当在cache中无法找到方法的IMP时,会调用_objc_msgForward函数进入慢速查找流程,这遍文章我们来分析一下慢速查找过程中,OC底层的处理逻辑
方法的查找
创建一个LGPerson、LGStudent和一个NSObject的分类NSObject+LGCate,其中LGPerson继承自NSObject,LGStudent继承自LGPerson,分别实现如下方法;
LGPerson声明并实现了sayNB和sayHappay
LGStudent声明并实现了sayHello和sayObjc
NSObject+LGCate声明并实现了sayEasy
我们在main函数中实现如下:
- 创建一个LGStudent的实例对象,并调用了sayHello和sayNB方法;
- LGStudent调用了类方法sayObjc和sayHappay;
- LGStudent调用类方法sayEasy.
下面我们执行代码,看结果:
解释:
- 实例对象可以调用自己的对象方法,也可以调用父类的对象方法;
- 类对象可以调用自己的类方法,也可以调用父类的类方法;
- LGStudent调用的是类方法sayEasy,为什么实际调用的是分类的对象方法sayEasy???
下面我们看一下isa走位图
在文章OC类方法归属分析我们已经知道,类方法归属于元类,通过如上的isa走位图可以发现,元类最终会继承于根元类,而根元类继承自NSObject,所以才有了LGStudent调用的是类方法sayEasy,实际调用的却是分类的对象方法sayEasy,
总结:
实例对象或类对象在调用方法时,会先在本类中查找,查不到时,再沿着继承链到父类中查找,最后找到
NSObject中。如果都没找到就会报错unrecognized selector sent to instance 0x1006059d0'
通过源码分析慢速查找流程
我们在objc4-781源码中全局搜索_objc_msgForward,如果如下:
查询到这么多结果,那么我们到底应该研究哪个呢?
执行如下代码,并断点
按住control键,然后鼠标左键点击如下图标:
多点几次,就会来到如下的位置:
因为say666并没有实现,所以往下走,找到_objc_msgSend_uncached,然后断点在这里:
按住control键,然后鼠标左键点击如下图标:
找到lookUpImpOrForward我们发现,其所在的文件名为objc-runtime-new.mm,在6099行代码.
这样我们成功的找到了我们要研究的源码对象objc-runtime-new.mm
在objc-runtime-new.mm搜索lookUpImpOrForward,找到其代码实现
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
const IMP forward_imp = (IMP)_objc_msgForward_impcache;
IMP imp = nil;
Class curClass;
runtimeLock.assertUnlocked();
// Optimistic cache lookup
if (fastpath(behavior & LOOKUP_CACHE)) {
imp = cache_getImp(cls, sel);
if (imp) goto done_nolock;
}
// 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();
// We don't want people to be able to craft a binary blob that looks like
// a class but really isn't one and do a CFI attack.
//
// To make these harder we want to make sure this is a class that was
// either built into the binary or legitimately registered through
// objc_duplicateClass, objc_initializeClassPair or objc_allocateClassPair.
//
// TODO: this check is quite costly during process startup.
checkIsKnownClass(cls);
if (slowpath(!cls->isRealized())) {
cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
// runtimeLock may have been dropped but is now locked again
}
if (slowpath((behavior & LOOKUP_INITIALIZE) && !cls->isInitialized())) {
cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
// runtimeLock may have been dropped but is now locked again
// 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
}
runtimeLock.assertLocked();
curClass = cls;
// The code used to lookpu the class's cache again right after
// we take the lock but for the vast majority of the cases
// evidence shows this is a miss most of the time, hence a time loss.
//
// The only codepath calling into this without having performed some
// kind of cache lookup is class_getInstanceMethod().
for (unsigned attempts = unreasonableClassCount();;) {
// curClass method list.
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
imp = meth->imp;
goto done;
}
if (slowpath((curClass = curClass->superclass) == nil)) {
// No implementation found, and method resolver didn't help.
// Use forwarding.
imp = forward_imp;
break;
}
// Halt if there is a cycle in the superclass chain.
if (slowpath(--attempts == 0)) {
_objc_fatal("Memory corruption in class list.");
}
// Superclass cache.
imp = cache_getImp(curClass, sel); // 有问题???? cache_getImp - lookup - lookUpImpOrForward
if (slowpath(imp == forward_imp)) {
// Found a forward:: entry in a superclass.
// Stop searching, but don't cache yet; call method
// resolver for this class first.
break;
}
if (fastpath(imp)) {
// Found the method in a superclass. Cache it in this class.
goto done;
}
}
// No implementation found. Try method resolver once.
if (slowpath(behavior & LOOKUP_RESOLVER)) {
behavior ^= LOOKUP_RESOLVER;
return resolveMethod_locked(inst, sel, cls, behavior);
}
done:
log_and_fill_cache(cls, imp, sel, inst, curClass);
runtimeLock.unlock();
done_nolock:
if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
return nil;
}
return imp;
}
下面我们分段分析
const IMP forward_imp = (IMP)_objc_msgForward_impcache;查找到objc_msgForward的imp,当最终没有找到目标方法的imp时,回调objc_msgForward的imp进行消息转发流程;imp = cache_getImp(cls, sel);再次走快速查找的流程是因为可能有多线程调用方法将目标方法的Imp进行了缓存,再查一次岂不更快。
- 先确定好
继承关系和isa指向,可以在后续快速的沿着继承链查找方法的Imp;- 确定当前的类,可以到
类的结构中查找方法的Imp.
- 先去类结构的
methodList中查找,找到就返回;- 没找到就沿着
继承链找到父类,此时同样要先去cache中查找,找到返回,没找到又会来到lookUpImpOrForward的慢速查找,从而形成递归查找;- 找到
NSObject都没有找到,imp指向objc_msgForward的imp,进入消息转发的流程.
进入消息转发
以上流程解释:找到了imp,将imp插入cache缓存,有关cache缓存流程,请看文章OC类结构之cache分析
总结
方法查找的慢速查找流程:
拿到objc_msgForward的imp -> 再次查找cache -> 确定继承关系 -> 去类的methodList中找 -> 去父类的cache中查找 -> 去父类的methodList中查找 -> 沿着继承链查找 —> 没找到进入消息转发 —> 找到了返回并写入cache。