上一篇 从汇编探索objc_msgSend 遗留了一个 __class_lookupMethodAndLoadCache3
,接下来就是对这个方法进行分析,也就会来到了消息查找流程,消息查找流程分为快速和慢速,快速查找已经在 objc_msgSend
找过了,找不到就会进入慢速查找,慢速查找流程会在 __class_lookupMethodAndLoadCache3
方法进行,可是我先不直接分析,先来点铺垫,想看源码分析的能够跳过第二步。缓存
咱们先建立 Student
和 Person
两个对象,Student
-> Person
-> NSObject
(->表明继承),而后在两个对象以及 NSObject
分类中各自添加一个实例方法和类方法。bash
#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
进行调用。 多线程
当汇编的 objc_msgSend
在缓存没有找到方法时,最终会走到这个方法进行慢速查找方法。app
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
/*
cls:若是是实例方法那就是类,若是是类方法那就是元类
sel:方法名
obj:方法调用者
*/
return lookUpImpOrForward(cls, sel, obj,
YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}
复制代码
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
方法,这个方法又是作什么的呢?立刻介绍。经过全局搜索,一步步查找来到 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;
复制代码
objc_msgSend
缓存中没有找到方法,就会来到 class_lookupMethodAndLoadCache3
进行慢速查找流程。lookUpImpOrForward
里面会先去本类当中查找方法 getMethodNoSuper_nolock
,本类没有找到就会去递归的去父类当中查找。_class_resolveMethod
,这是苹果爸爸给咱们的最后一次机会。_objc_forward_handler
,而后崩溃报错 selector sent to instance
。