ios 消息查找流程-慢速查找
上篇文章ios objc_msgSend分析也就是消息查找的快速流程,这篇文章我们就来分析一下消息查找的慢速流程。
上篇文章我们探索到慢速查找进入到__class_lookupMethodAndLoadCache3
这个方法中,但是我们最开始不知道进入到这个方法,那我们怎么探索呢?有办法的
1、怎么进入到__class_lookupMethodAndLoadCache3
?
首先打开源码,创建一个类,添加一个对象方法,在main.m
中调用一下,打上断点,然后在Debug
->Debug Workflow
->Always Show Disassembly
进入汇编:
继续走:
WX20191229-213317@2x.png
找到了
__objc_msgSend_uncached
方法,继续走:WX20191229-213348@2x.png
我们找到了::_class_lookupMethodAndLoadCache3
的方法。
这就是我们探索未知的一种思路。
2、_class_lookupMethodAndLoadCache3
源码是怎么执行的?
根据上面的一系列操作,我们知道了方法查找是进入到_class_lookupMethodAndLoadCache3
方法,在源码中搜索:
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
return lookUpImpOrForward(cls, sel, obj,
YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}
在源码中,首先我们看到会调用一个lookUpImpOrForward
的函数,而且cache
会传NO
,这里传NO
是因为在快速查找中没有找到方法,也就是没有命中缓存,如果命中缓存了就不会进入到这个慢速查找了。我们在看一下lookUpImpOrForward
源码:
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;
}
// 以上部分是判断在缓存中有没有命中缓存,命中则直接返回imp
// 所以下面才是我们要进行慢速分析的重点
runtimeLock.lock(); // 这里加锁是为了防止多线程出现错乱
checkIsKnownClass(cls);// 这句是对class进行一个判断是否合法
if (!cls->isRealized()) {// 判断类中的以一些准备条件是否完成,准备好ro/rw/父类/元类
realizeClass(cls);
}
if (initialize && !cls->isInitialized()) {// 这里也是跟上面判断一样,也是在做一些准备工作
runtimeLock.unlock();
_class_initialize (_class_getNonMetaClass(cls, inst));
runtimeLock.lock();
}
// 这里开始进行真正的查找
retry:
runtimeLock.assertLocked();
// Try this class's cache.
imp = cache_getImp(cls, sel);
if (imp) goto done;
// Try this class's method lists.
{ // 这里加两个大括号的意思是形成“局部作用域”,和下面的方法不冲突
// 这里我们可以看出是从class里面进行查找
// 调用getMethodNoSuper_nolock函数下面我们具体分析
Method meth = getMethodNoSuper_nolock(cls, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, cls);
imp = meth->imp;
goto done;
}
// 这里这个方法的作用就是在类中进行查找,如果找到则进行缓存填充,返回imp
// log_and_fill_cache->cache_fill->cache_fill_nolock 这样的一个流程
}
// Try superclass caches and method lists.
{ // 这里就是类中没有找到,找父类,父类没找到找NSObject
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) {// 如果找到imp进行判断
if (imp != (IMP)_objc_msgForward_impcache) {// 判断其是否是消息转发 不是消息转发,走log_and_fill_cache,是消息转发直接跳出循环
// 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.
// 没有找到imp则进行父类方法列表查找,跟上面在类中查找相似
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.
// 如果最后方法没找到,就开始调用这个_objc_msgForward_impcache
imp = (IMP)_objc_msgForward_impcache;
cache_fill(cls, sel, imp, inst);
done:
runtimeLock.unlock();
return imp;
}
-
刚上面分析可能会有人说:
if (!cls->isRealized()) {
realizeClass(cls);
}
这里为什么要准备父类和元类,这里是为了方便我们进行下次查找,如果类本身中没有,我们要往父类中查找,或者查找类方法我们要去元类中查找。
-
进行
getMethodNoSuper_nolock
函数调用,我们看一下源码
static method_t *
getMethodNoSuper_nolock(Class cls, SEL sel)
{
runtimeLock.assertLocked();
assert(cls->isRealized());
// fixme nil cls?
// fixme nil sel?
for (auto mlists = cls->data()->methods.beginLists(),
end = cls->data()->methods.endLists();
mlists != end;
++mlists)
{
method_t *m = search_method_list(*mlists, sel);
if (m) return m;
}
return nil;
}
这里就是通过对类内部data()
里面的methods
方法列表进行遍历查找,找到当前sel
对应的method_t
则返回,其中间有进行了一个search_method_list
方法,我们继续探索:
static method_t *findMethodInSortedMethodList(SEL key, const method_list_t *list)
{
assert(list);
const method_t * const first = &list->first;
const method_t *base = first;
const method_t *probe;
uintptr_t keyValue = (uintptr_t)key;
uint32_t count;
for (count = list->count; count != 0; count >>= 1) {
probe = base + (count >> 1);
uintptr_t probeValue = (uintptr_t)probe->name;
if (keyValue == probeValue) {
// `probe` is a match.
// Rewind looking for the *first* occurrence of this value.
// This is required for correct category overrides.
while (probe > first && keyValue == (uintptr_t)probe[-1].name) {
probe--;
}
return (method_t *)probe;
}
if (keyValue > probeValue) {
base = probe + 1;
count--;
}
}
return nil;
}
这里面进行一个有序的二分查找算法进行查找method_t
。这里查找到method_t
之后进行方法缓存log_and_fill_cache->cache_fill->cache_fill_nolock
这样一个过程,可借鉴ios cache_t分析。
-
调用
_objc_msgForward_impcache
,点进去返现只能进入到
#if !OBJC_OLD_DISPATCH_PROTOTYPES
extern void _objc_msgForward_impcache(void);
#else
extern id _objc_msgForward_impcache(id, SEL, ...);
#endif
全局搜索发现又进入到了汇编:
STATIC_ENTRY __objc_msgForward_impcache
// No stret specialization.
b __objc_msgForward // 在这里发现调用了 往下看
END_ENTRY __objc_msgForward_impcache
ENTRY __objc_msgForward // 在这里调用了,然后里面有调用了__objc_forward_handler,这个全部查找发现全是调用,我们用最开始的方法全局搜索 _objc_forward_handler
adrp x17, __objc_forward_handler@PAGE
ldr p17, [x17, __objc_forward_handler@PAGEOFF]
TailCallFunctionPointer x17
END_ENTRY __objc_msgForward
继续看_objc_forward_handler
,也全是调用,全局搜索:
// Default forward handler halts the process.
__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;
会发现这样一段代码,unrecognized selector sent to instance
这一句是不是很熟悉,这是我们项目crash之后输出的吗?对的,如果方法处理失败,就会报出次错误。
总结:
- 消息的慢速查找流程首先从类和父类的缓存列表进行查找,然后在方法列表进行查找。
- 直到查找到NSObject中都没有找到方法,进行方法解析,消息转发处理失败之后则报经典错误错
unrecognized selector sent to instance