前言
在iOS底层原理–RunTime之objc_msgSend探究(快速查找)这篇文章中,我们已经分析了消息的快速查找流程,如果在快速查找流程中没有找到,就会进入到__objc_msgSend_uncached
,也就是慢速查找流程。下面?我们就进入到慢速查找流程就行分析。
源码分析
我们先进入源码工程,找到__objc_msgSend_uncached
的汇编源码,发现去掉注释有用的代码就两行。
我们先来看下TailCallFunctionPointer x17
方法,发现该方法只是跳转返回x17的值。
具体x17里存的是什么,我们还不知道,这就得看下第一个方法MethodTableLookup
做了哪些操作。字面意思方发表查找
,我们进入源码看下。
此处x16是class,这个大家需要记得。在源码中,我们发现_lookUpImpOrForward
方法的返回值,会被给x17寄存器,说明该方法返回的极有可能就是imp
。
我们查找_lookUpImpOrForward
方法,发现未在源码中找到,那么可能是汇编调用了c/c++方法,去掉一个下划线继续查找看看。
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
/*
定义的消息转发
behavior==3 = LOOKUP_INITIALIZE|LOOKUP_RESOLVER
*/
const IMP forward_imp = (IMP)_objc_msgForward_impcache;
IMP imp = nil;
Class curClass;
runtimeLock.assertUnlocked();
/*
判断类是否已经初始化,如果没有初始化的话
behavior = LOOKUP_NOCACHE|LOOKUP_INITIALIZE|LOOKUP_RESOLVER
*/
if (slowpath(!cls->isInitialized())) {
behavior |= LOOKUP_NOCACHE;
}
//加锁,目的是保证读取的线程安全
runtimeLock.lock();
//是否是已知的类,也就是说该类是否已经加载过了
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 (unsigned attempts = unreasonableClassCount();;) {
////判断是否有共享缓存缓存优化
if (curClass->cache.isConstantOptimizedCache(/* strict */true)) {
#if CONFIG_USE_PREOPT_CACHES
/*
如果是真机,再走一次快速查找流程,
可能在走的这步的过程中,别的线程调用了该方法,严谨一些
*/
imp = cache_getImp(curClass, sel);
//如果缓存中找到imp,则跳转到done_unlock
if (imp) goto done_unlock;
curClass = curClass->cache.preoptFallbackClass();
#endif
} else {
// curClass method list.
//采用二分法查找methodList
Method meth = getMethodNoSuper_nolock(curClass, sel);
//如果找到了,则获取imp跳到done.
if (meth) {
imp = meth->imp(false);
goto done;
}
//直到当前类的父类为nil为止(也就是NSObject),将定义的消息转发赋值给imp,跳出循环
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.
// 从父类cache中找(快速查找)
imp = cache_getImp(curClass, sel);
//如果父类返回的是forward_imp 停止查找,跳出循环
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;
}
//如果父类找到了imp,则跳到done
if (fastpath(imp)) {
// Found the method in a superclass. Cache it in this class.
goto done;
}
}
// No implementation found. Try method resolver once.
/*
behavior = 3 LOOKUP_RESOLVER = 2
3&2 = 0011 & 0010 = 0010 与操作全部为1才为1
3^2 = 0011 ^ 0010 = 0001 = behavior
所以第二次进来 1 & 2 = 0001 & 0010 = 0000 条件不成立也就进不来了
*/
if (slowpath(behavior & LOOKUP_RESOLVER)) {
behavior ^= LOOKUP_RESOLVER;
//动态方法决议
return resolveMethod_locked(inst, sel, cls, behavior);
}
done:
// 3 & 8 = 0011 & 1000 = 0000 = 0
if (fastpath((behavior & LOOKUP_NOCACHE) == 0)) {
#if CONFIG_USE_PREOPT_CACHES
while (cls->cache.isConstantOptimizedCache(/* strict */true)) {
cls = cls->cache.preoptFallbackClass();
}
#endif
//将查询到的sel和imp插入到缓存 注意:插入的是当前类的缓存
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;
}
复制代码
源码中已经标明注释
,下面我们进行一下文字总结。
慢速查找流程总结
- 定义消息转发IMP
- 判断类是否已经初始化
- 类是否已加载
- 类是否已实现,确定父类/元类链路,方便进行递归查找
- 递归查找
- 判断是否有共享缓存优化
- 如果有,走快速查找流程,找到后,插入缓存返回IMP
- 如果没有,进行二分法在当前类中methodList中找method,如果找到,则返回对应的IMP,如果没有找到,继续执行。直到找到NSObject,跳出循环。
- 如果父类链中有循环,则抛异常停止
- 在父类cache中快速查找IMP
- 如果父类返回的是forward_imp 停止查找,跳出循环
- 如果父类找到了imp,则将查询到的sel和imp插入到缓存 注意:插入的是当前类的缓存
- 如果cls以及父类都没有查询到,此时系统会给你一次机会,判断是否执行过动态方法决议,如果没有则走动态方法决议。
- 判断是否有共享缓存优化
各个方法详解
checkIsKnownClass()
这个方法主要是判断,当前类是否已经注册加载进来了,通过下面的源码,我们得出如果当前类未知, 直接返回错误信息”Attempt to use unknown class XXXX ….”
realizeAndInitializeIfNeeded_locked(id inst, Class cls, bool initialize)
我们通过源码发现,主要实现方法是其中实现和初始化的类方法,下面我们分别看下这两个方法。
realizeClassMaybeSwiftAndLeaveLocked(Class cls, mutex_t& lock)
通过源码我们发现realizeClassMaybeSwiftMaybeRelock
及realizeClassWithoutSwift
这两个方法,其实就是递归实现下继承链和isa走位链相关的类
initializeAndLeaveLocked
通过源码我们发现initializeAndMaybeRelock
及initializeNonMetaClass
这两个方法,其实就是递归初始化继承链和isa走位链相关的类
对象方法: 先从当前类中找,没有接着去父类中找,还没有再接着去爷爷类中找->...->最后到根类中找。
类方法: 先从当前类的元类中找,没有去父元类中找->...->没有再去根元类找。
二分查找法
findMethodInSortedMethodList
前提:
非常重要的一个前提是,列表已经经过排序了,否则无法使用二分法。
举个?
假如list的count=8,我们要找sel在第2位。
第1次循环
- count = 8, base = 0, probe = 0+4
- 2!=4
- 2<4
第2次循环
- count = 8>>1 = 4; base = 0,probe = 0+2
- 2==2,找到方法,返回
再举个?
假如list的count=8,我们要找sel在第7位。
第1次循环
- count = 8, base = 0, probe = 0+4
- 7!=4
- 7>4, base = 4+1 = 5, count = 8-1 = 7
第2次循环
- count = 7>>1 = 3, base = 5, probe = 5+1 = 6
- 7!=6
- 7>6, base = 6+1 = 7, count = 3-1 = 2
第3次循环
- count = 2>>1 = 1, base = 7, probe = 7+0 =7
- 7==7,找到方法,返回
大致画个流程图出来吧,是时候展现下才华了。
cache_getImp
我们找到源码,发现也是汇编实现的。
我们看下此处的CacheLookUp是否和之前快速查找的一样。
通过源码,我们发现,如果cache没有命中,会直接返回nil,而不会进入慢速查找流程中。这是最大的区别。
_objc_msgForward_impcache
我们如果方法未实现,总是会抛出同一个错误 unrecognized selector sent to instance
,这是为什么呢?我们分析下这个方法的源码,就可以得出结论了。
这里发现主要是__objc_forward_handler
,我们找下这个方法的源码。
一切都清晰了。
全文总结
- 实例方法(对象方法),先在当前类中进行快速查找。
- 如果在cache中找不到,会进行慢速查找。
- 慢速找到:方法存入cache,返回imp。
- 找不到:依次类–父类–根类–nil,父类只会cache中找,找不到直接返回nil。
- 找到,直接返回IMP。
- 如果在cache中找不到,会进行慢速查找。
- 类方法,先在元类中快速查找,其慢速查找的父类链是:元类–根元类–根类–nil
- 如果在cache中找不到,会进行慢速查找。
- 找到:方法存入cache,返回imp。
- 找不到:依次元类–根元类–根类–nil。
- 找到,直接返回IMP。
- 如果在cache中找不到,会进行慢速查找。
- 如果快速查找、慢速查找都找不大方法的实现(IMP),则尝试
动态方法决议
。 - 如果动态方法决议依然找不到,则会进行
消息转发
。
最后两步预知后事如何,且看下回分解。