objc_msgSend慢速查找流程
当消息在cache里面找不到的时候会触发MissLabelDynamic
,从代码CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached
可以看出这个参数代表的实际参数就是__objc_msgSend_uncached
STATIC_ENTRY __objc_msgSend_uncached
UNWIND __objc_msgSend_uncached, FrameWithNoSaves
MethodTableLookup
TailCallFunctionPointer x17
END_ENTRY __objc_msgSend_uncached
我们继续看MethodTableLookup
的实现
.macro MethodTableLookup
// lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER)
// receiver and selector already in x0 and x1
mov x2, x16
mov x3, #3
bl _lookUpImpOrForward
// IMP in x0
mov x17, x0
RESTORE_REGS MSGSEND
.endmacro
我们看到IMP在X0里面返回,说明_lookUpImpOrForward
是关键信息,我们继续找_lookUpImpOrForward
的实现
lookUpImpOrForward流程
1、先获取forward_imp
,后面找不到的时候返回forward_imp
2、检查类是否已经被注册
3、对类进行第一次初始化,分配分配读写数据,返回类的真实结构
4、for循环查找,先查找此时缓存内是否有方法。如果存在通过cache_getImp()
查找返回
5、查找当前类的method list
找到的话就返回
6、如果还是找不到就curClass = curClass->getSuperclass()
(一直找到NSObject)通过cache_getImp()
去父类的缓存里面找
7、如果无论怎么都找不到,尝试方法解析器一次(单利)
8、如果在当中找到, goto done
,返回imp
并且调用log_and_fill_cache()
缓存下来
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
//获取forward_imp,当在类里面也找不到方法的时候返回forward_imp
const IMP forward_imp = (IMP)_objc_msgForward_impcache;
IMP imp = nil;
Class curClass;
//检车类是否已经被注册
checkIsKnownClass(cls);
cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);
// runtimeLock may have been dropped but is now locked again
runtimeLock.assertLocked();
curClass = cls;
//一个for的死循环,如果在里面有goto或者break等的时候会跳出循环
for (unsigned attempts = unreasonableClassCount();;) {
//防止此时方法被插进去,再做一个快速缓存查找
if (curClass->cache.isConstantOptimizedCache(/* strict */true)) {
imp = cache_getImp(curClass, sel);
if (imp) goto done_unlock;
curClass = curClass->cache.preoptFallbackClass();
} else {
//缓存中没有,去method list.里面查找
//先for循环,后面会进入二分法查找流程
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {//找到之后返回
imp = meth->imp(false);
goto done;//具体走log_and_fill_cache,再次insert插入缓存
}
//上面去自己的methodlist找不到,就去获取父类的里面找->父类的父类找->找到NSObject
//如果还找不到,返回forward_imp
if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
imp = forward_imp;
break;
}
}
// 父类缓存查找,找不到返回nil
//curClass = curClass->getSuperclass()不断找父类的过程
imp = cache_getImp(curClass, sel);
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:
if (fastpath((behavior & LOOKUP_NOCACHE) == 0)) {
\#if CONFIG_USE_PREOPT_CACHES
while (cls->cache.isConstantOptimizedCache(/* strict */true)) {
cls = cls->cache.preoptFallbackClass();
}
\#endif
log_and_fill_cache(cls, imp, sel, inst, curClass);
}
done_unlock:
runtimeLock.unlock();
if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
return nil;
}
return imp;
}
findMethodInSortedMethodList()实现
在查找过程中会调用findMethodInSortedMethodList()
方法,我们分析一下算法实现过程(二分法)
假设count = 8,我们要找的数在第6位(可以任意,这里举例说明)
1、count >>= 1
count按位右移,相当于➗2
2、probe = base + (count >> 1)
,相当于probe=0(base)+4(count)=4
3、如果6>4,base = probe + 1``count--
,相当于base=4+1=5,count=7
4、再次进入for循环count=3, probe= 5+(3>>1)= 5+1=6,此时命中(keyValue == probeValue)
5、while (probe > first && keyValue == (uintptr_t)getName((probe - 1))) { probe--;}
这个意思就是有多个相同的方法时,返回最前面的方法。其实就是返回分类的方法。如果有多个分类会返回最后编译进去的分类。编译顺序可以在Compile Sources
中调整测试。
findMethodInSortedMethodList(SEL key, const method_list_t *list, const getNameFunc &getName)
{
ASSERT(list);
auto first = list->begin();
auto base = first;
decltype(first) probe;
uintptr_t keyValue = (uintptr_t)key;
uint32_t count;
// 1000 - 0100
// 8 - 1 = 7 >> 0111 -> 0011 3 >> 1 == 1
// 0 + 4 = 4
// 5 - 8
// 6-7
for (count = list->count; count != 0; count >>= 1) {
probe = base + (count >> 1);
uintptr_t probeValue = (uintptr_t)getName(probe);
if (keyValue == probeValue) {
//返回分类的方法
while (probe > first && keyValue == (uintptr_t)getName((probe - 1))) {
probe--;
}
return &*probe;
}
if (keyValue > probeValue) {
base = probe + 1;
count--;
}
}
return nil;
}
类方法和实例方法
我们新建一个NSObject的分类NSObject+EL
我们在里面实现两个方法
- (void)sayNB{
NSLog(@"NSObject===%s",__func__);
}
+ (void)sayHello{
NSLog(@"NSObject===%s",__func__);
}
新建一个ELPerson
类继承于NSObject,我们直接调用如下代码
int main(int argc, const char * argv[]) {
@autoreleasepool {
ELPerson *p = [ELPerson alloc];
[ELPerson performSelector:@selector(sayHello)];
[ELPerson performSelector:@selector(sayNB)];
NSLog(@"Hello, World!");
}
return 0;
}
2021-07-01 11:31:23.567714+0800 KCObjcBuild[70678:3948791] NSObject===+[NSObject(EL) sayHello]
2021-07-01 11:31:23.568099+0800 KCObjcBuild[70678:3948791] NSObject===-[NSObject(EL) sayNB]
可以看出来两个都调用成功了,在oc底层其实没有“+”
和“-”
方法的区分,只是方法存储的位置不同而已。我们具体分析下调用成功的流程:
1、NSObject中sayHello
是类方法,在NSObject的元类
(Root Class Meta)中存储,ELPerson调用sayHello
方法,会从ELPerson的元类中找,找不到的话。从元类的父类找(Super Class Meta),继续找根元类(Root Class Meta),此时找到imp
,返回。
2、NSObject中sayNB
的实例方法,在NSObject
的类中存储,ELPerson调用sayNB
,会从ELPerson的元类中找,找不到的话。从元类的父类找(Super Class Meta),继续找根元类(Root Class Meta),再继续找(Root Class class)找到NSObject,此时找到imp
,返回。
我们再进一步,用实例对象p调用看看回事什么结果
int main(int argc, const char * argv[]) {
@autoreleasepool {
ELPerson *p = [ELPerson alloc];
//[p performSelector:@selector(sayHello)];
[p performSelector:@selector(sayNB)];
NSLog(@"Hello, World!");
}
return 0;
}
我们发现sayNB调用成功了,sayHello直接崩溃,找不到方法
具体分析一下调用流程:
3、sayNB
调用成功,我们很好理解。直接ELPerson
继承于NSObject,ELPerson里面没有实现,直接找到父类的实现。
4、sayHello
没有调用成功,我们前面分析过sayHello存在NSObject的元类
(Root Class Meta)当中,对象去调用方法的时候,先查找ELPerson
类中查找,此时没有,再往父类(NSObject)中查找,我们发现此时也没有。再找NSObject的父类就是nil了,所以查找不到。因为这个流程并没有查找到NSObject的元类
(Root Class Meta)当中去,所以找不到。
附isa的走位图,可以帮助我们分析: