经过上一篇 消息快速发送之 objc_msgSend 的分析 对调用方法的时候,通过汇编查询 Cache 如果 缓存命中 就直接进行发送。
但是上一篇的结尾说到,如果没有命中缓存就需要走慢速流程,也就是来到 _lookUpImpOrForward 函数里面,接下来就是对慢速流程的探索。
1、进入慢速转发 _lookUpImpOrForward
这里没有写东西是以为 818.2 源码改动了,所以改了标题并且删除了原来的文字,主要发懒不想再排版了,哈哈。
2、lookUpImpOrForward()函数的探索
看函数名称就知道这里是查找方法的实现或者消息转发,一起进入这个函数看看吧,这个函数很长,我们慢慢分析。
/**
* lookUpImpOrForward。
* 标准IMP查找。
* inst是cls或其子类的一个实例,如果不知道,则为nil。
* 如果cls是一个未初始化的元类,那么非空的inst会更快。
* 可能返回_objc_msgForward_impcache。用于外部使用的imp必须转换为_objc_msgForward或_objc_msgForward_stret。
*/
NEVER_INLINE
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();
//如果没有初始化类
if (slowpath(!cls->isInitialized())) {
behavior |= LOOKUP_NOCACHE;
}
// runtimeLock 在isrealize和isInitialized检查过程中被持有,以防止对并发实现的竞争。
// runtimeLock 在方法搜索过程中保持,使方法查找+缓存填充原子相对于方法添加。
// 否则,可以添加一个类别,但是无限期地忽略它,因为在代表类别的缓存刷新之后,缓存会用旧值重新填充。
// 上方的说明就是对这里加锁的解释
runtimeLock.lock();
// 如果运行时知道这个类(位于共享缓存中,加载的图像的数据段中,或者已经用obj_allocateClassPair分配了),
// 则返回true,
// 如果没有就崩溃了
checkIsKnownClass(cls);
cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);
// runtimeLock may have been dropped but is now locked again
runtimeLock.assertLocked();
curClass = cls;
//获取锁后,代码再次查找类的缓存,但绝大多数情况下,证据显示大多数情况下这是一个遗漏,因此时间损失。
//唯一没有执行某种缓存查找的代码路径是class_getInstanceMethod()。
for (unsigned attempts = unreasonableClassCount();;) {
if (curClass->cache.isConstantOptimizedCache(/* strict */true)) {
#if CONFIG_USE_PREOPT_CACHES
// 先查询一遍缓存 如果 imp 存在,就直接返回了
imp = cache_getImp(curClass, sel);
// 如果imp 存在就跳转 done_unlock,done_unlock里面就2行代码,
if (imp) goto done_unlock;
curClass = curClass->cache.preoptFallbackClass();
#endif
} else {
// curClass method list.
// 在该对象的所属的类的方法列表中查找
//使用二分查找法
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
imp = meth->imp(false);
goto done;
}
//把curClass 指向父类
if (slowpath((curClass = curClass->getSuperclass()) == 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);
///低概率找不到
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;
}
3、 二分查找法的分析 getMethodNoSuper_nolock ()
苹果在查找方法列表的时候使用了 二分查找法,二分查找法有一个前提条件就是 有序数组,我们经常能看到苹果将方法的 SEL sel 强转成整形,这里整形就是有序的~。
有了这个前提,我们看看 getMethodNoSuper_nolock 的代码:
1、getMethodNoSuper_nolock()
/***********************************************************************
* getMethodNoSuper_nolock
* fixme
* Locking: runtimeLock must be read- or write-locked by the caller
**********************************************************************/
static method_t *getMethodNoSuper_nolock(Class cls, SEL sel)
{
runtimeLock.assertLocked();
ASSERT(cls->isRealized());
// fixme nil cls?
// fixme nil sel?
//二分查找
auto const methods = cls->data()->methods();
for (auto mlists = methods.beginLists(),
end = methods.endLists();
mlists != end;
++mlists)
{
method_t *m = search_method_list_inline(*mlists, sel);
if (m) return m;
}
return nil;
}
2、findMethodInSortedMethodList ()
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) {
while (probe > first && keyValue == (uintptr_t)probe[-1].name) {
probe--;
}
return (method_t *)probe;
}
if (keyValue > probeValue) {
base = probe + 1;
count--;
}
}
return nil;
}
分析一下上述逻辑:
/**
* count : 初始值为方法列表的个数 假设 48
* 1、如果 count != 0; 循环条件每次 右移一位 也就是说 除以 2;
* 2、 第一次进入 从一半开始找起,如果 keyValue > probeValue 那么在右边,否则在左边;
* 3、 第二次是从 12 开始找起,也不满足 keyValue > probeValue 的条件;
* 4、 第二次从 6 开始找起,满足条件 keyValue > probeValue,将初始值移动到当前 6 的后一位
也就是从 7 开始查找,然后count--,
可以看到当前 count = 5 ,然后在对 > 6 且 < 12 进行查找,
也就是 7 - 11 ,count >> 1 为 2, 7+2 = 9,刚好是 7 - 11 的中心。
* 5、这就是 2分查找法,但是前提是有序数组。
*/
4、没有实现的方法 Xcode 是怎么崩溃的?
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
//...
for (unsigned attempts = unreasonableClassCount();;) {
if (curClass->cache.isConstantOptimizedCache(/* strict */true)) {
//...
} else {
// curClass method list.
// 在该对象的所属的类的方法列表中查找
//...
//把curClass 指向父类
if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
// No implementation found, and method resolver didn't help.
// Use forwarding.
imp = forward_imp;
break;
}
}
//...
// Superclass cache.
// 寻找父类的缓存中有没有
imp = cache_getImp(curClass, sel);
//...
///大概率能找到
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)) {
//如果找到了就填充到缓存中
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;
}
上方方法列表的查找就不需要多说了,我们看到了 resolveMethod_locked 消息转发,我们下一篇说明,还有一段就是 (curClass = curClass->getSuperclass()) == nil) 后 imp = (IMP)_objc_msgForward_impcache; ,这是一段汇编。搜索 _objc_msgForward_impcache,看到如下代码:
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
上述代码发现调用了 _objc_forward_handler 函数,继续搜索,得到如下结果:
void *_objc_forward_handler = (void*)objc_defaultForwardHandler;
// 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);
}
呵~,这不就是我们经常看到的 Xcode 找不到方法的崩溃信息吗?
到这里,消息查找流程就全部完成了,下一篇会探索 如果方法没有实现,OC 是怎么消息转发的。
PS:可以运行的并且不断进行注释的objc 源码地址。