1、前言
上一篇 从汇编探索objc_msgSend 遗留了一个 __class_lookupMethodAndLoadCache3,接下来就是对这个方法进行分析,也就会来到了消息查找流程,消息查找流程分为快速和慢速,快速查找已经在 objc_msgSend 找过了,找不到就会进入慢速查找,慢速查找流程会在 __class_lookupMethodAndLoadCache3 方法进行,但是我先不直接分析,先来点铺垫,想看源码分析的可以跳过第二步。
2、isa 走位图
我们先创建 Student 和 Person 两个对象,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 进行调用。

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。