在【iOS底层原理:objc_msgSend之缓存查找】中我们分析到,如果缓存没有命中的时候,会默认找到_lookUpImpOrForward
方法。
其实如果看过源码,我们都知道应该lookUpImpOrForward
就是我们慢速查找方法的入口方法了。那么我们新手如何知道是这个方法呢?
1、查找lookUpImpOrForward方法
1.1、打断点到我们调用的任意一个方法
1.2、在Xcode菜单栏中选择Debug -> Debug Workflow -> Always Show Disassembly,然后断点到 objc_msgSend
方法处
1.3、然后 【control + step into】 进入objc_msgSend
方法
从快速查找的过程中,其实我们知道了如果缓存未命中,会走到_objc_msgSend_uncached
方法中,所以,我们直接断点到该方法处
1.4、【control + step into】 进入_objc_msgSend_uncached
方法
在改方法的汇编中,我们可以发现了我们需要找的方法lookUpImpOrForward
其实,在oc中,当由汇编到c++的时候,会减少一个_
,当c++到c方法的时候,会再次减少一个_
。
2、lookUpImpOrForward 分析
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();
// Optimistic cache lookup
if (fastpath(behavior & LOOKUP_CACHE)) {
imp = cache_getImp(cls, sel);
if (imp) goto done_nolock;
}
runtimeLock.lock();
checkIsKnownClass(cls);
if (slowpath(!cls->isRealized())) {
cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
}
if (slowpath((behavior & LOOKUP_INITIALIZE) && !cls->isInitialized())) {
cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
}
runtimeLock.assertLocked();
curClass = cls;
for (unsigned attempts = unreasonableClassCount();;) {
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
imp = meth->imp;
goto done;
}
if (slowpath((curClass = curClass->superclass) == nil)) {
imp = forward_imp;
break;
}
if (slowpath(--attempts == 0)) {
_objc_fatal("Memory corruption in class list.");
}
imp = cache_getImp(curClass, sel);
if (slowpath(imp == forward_imp)) {
break;
}
if (fastpath(imp)) {
goto done;
}
}
if (slowpath(behavior & LOOKUP_RESOLVER)) {
behavior ^= LOOKUP_RESOLVER;
return resolveMethod_locked(inst, sel, cls, behavior);
}
done:
log_and_fill_cache(cls, imp, sel, inst, curClass);
runtimeLock.unlock();
done_nolock:
if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
return nil;
}
return imp;
}
2.1、cache_getImp
首先会从缓存中进行查找,Why?
因为我们在整个运行过程中,是多线程的,可能在正好在调用的时候,该方法已经在其它线程中已经加入缓存了。
2.2、checkIsKnownClass
主要是判断当前类是否已经加载到内存中了。
2.3、realizeClassMaybeSwiftAndLeaveLocked
主要是bits
的赋值,将ro
赋值给rw
,并且内部通过realizeClassWithoutSwift
将整个继承链都确定下来,方便我们后续进行慢速查找的过程中,可以通过继承链来查找。
2.3.1、realizeClassWithoutSwift 分析
auto ro = (const class_ro_t *)cls->data();
auto isMeta = ro->flags & RO_META;
if (ro->flags & RO_FUTURE) {
// This was a future class. rw data is already allocated.
rw = cls->data();
ro = cls->data()->ro();
ASSERT(!isMeta);
cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
} else {
// Normal class. Allocate writeable class data.
rw = objc::zalloc();
rw->set_ro(ro);
rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
cls->setData(rw);
}
上面的代码主要是进行了rw
数据的赋值。
cls->superclass = supercls;
cls->initClassIsa(metacls);
if (supercls && !isMeta) reconcileInstanceVariables(cls, supercls, ro);
cls->setInstanceSize(ro->instanceSize);
if (ro->flags & RO_HAS_CXX_STRUCTORS) {
cls->setHasCxxDtor();
if (! (ro->flags & RO_HAS_CXX_DTOR_ONLY)) {
cls->setHasCxxCtor();
}
}
if ((ro->flags & RO_FORBIDS_ASSOCIATED_OBJECTS) ||
(supercls && supercls->forbidsAssociatedObjects()))
{
rw->flags |= RW_FORBIDS_ASSOCIATED_OBJECTS;
}
if (supercls) {
addSubclass(supercls, cls);
} else {
addRootClass(cls);
}
methodizeClass(cls, previously);
上面的代码主要是进行了cls
结构的确定。
从cls->superclass = supercls;
和addSubclass(supercls, cls);
可以看出其实cls
是一个双向链表结构。
methodizeClass(cls, previously);
其实是对rw
进行处理,将所有的方法
、属性
、协议
attachLists 到rw
上
2.4、initializeAndLeaveLocked
主要是初始化所有类,即递归调用initialize
方法。
整个流程如下:
initializeAndLeaveLocked
-> initializeAndMaybeRelock
-> initializeNonMetaClass
-> callInitialize
-> ((void(*)(Class, SEL))objc_msgSend)(cls, @selector(initialize));
2.5、imp 分析
为什么这一步是分析imp
呢?
其实整个的imp
主要是在for循环
中。这个for循环
是一层死循环。
2.5.1、分析for死循环
- 1、getMethodNoSuper_nolock
先从当前类进行查找。
static method_t *
getMethodNoSuper_nolock(Class cls, SEL sel)
{
runtimeLock.assertLocked();
ASSERT(cls->isRealized());
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;
}
从上面的代码中,我们可以看到其实是从cls
的data
的methods
方法中进行二分查找
。
通过 getMethodNoSuper_nolock
-> search_method_list_inline
-> findMethodInSortedMethodList
流程找到二分查找
的源码实现如下:
ALWAYS_INLINE 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); // 右移1位,就是除2
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;
}
probe = base + (count >> 1);
右移1位,就是除2;
while
循环中其实查找同名方法,即查找添加的所有的分类,找到最后一个添加的分类的方法实现;
- 2、当在当前类中没找到方法时,会首先调用
curClass = curClass->superclass
将当前类的父类赋值给当前类,即下一步回去父类进行查找。 - 3、然后会调用
imp = cache_getImp(curClass, sel);
去查找父类的缓存- 当我们点进去的时候,发现实现如下:
extern IMP cache_getImp(Class cls, SEL sel);
到了此处我们又是找不到去哪里了,那么根据我们的经验,可以推测下,查找缓存应该还会是快速查找,即汇编实现,那么根据第一步的流程,我们全局搜索下_cache_getImp
。
_cache_getImp
汇编实现如下:
STATIC_ENTRY _cache_getImp
GetClassFromIsa_p16 p0
CacheLookup GETIMP, _cache_getImp
LGetImpMiss:
mov p0, #0
ret
END_ENTRY _cache_getImp
GetClassFromIsa_p16 p0
: p0也就是我们的curClass
的isa
。
然后CacheLookup
其实就是我们以前博客中提到的快速查找的流程了,具体查找过程可以看【iOS底层原理:objc_msgSend之缓存查找】
其中唯一的差异性就是,传入的参数,_objc_msgSend
缓存查找的传参为NORMAL
,_cache_getImp
的方法调用GETIMP
。
// _objc_msgSend 方法
CacheLookup NORMAL, _objc_msgSend
// _cache_getImp 方法
CacheLookup GETIMP, _cache_getImp
当缓存中没找到的时候,会找到JumpMiss
或者CheckMiss
中;
.macro CheckMiss
// miss if bucket->sel == 0
.if $0 == GETIMP
cbz p9, LGetImpMiss
.elseif $0 == NORMAL
cbz p9, __objc_msgSend_uncached
.elseif $0 == LOOKUP
cbz p9, __objc_msgLookup_uncached
.else
.abort oops
.endif
.endmacro
.macro JumpMiss
.if $0 == GETIMP
b LGetImpMiss
.elseif $0 == NORMAL
b __objc_msgSend_uncached
.elseif $0 == LOOKUP
b __objc_msgLookup_uncached
.else
.abort oops
.endif
.endmacro
通过上面的代码可以知道最后是调用了LGetImpMiss
LGetImpMiss:
mov p0, #0
ret
END_ENTRY _cache_getImp
在LGetImpMiss
方法中,其实就返回了p0
,也就是当前的cls
,即父类的cls
那然后会一直走1、2、3、的流程,直到跳出循环,也就是走到4
- 4、如果当
imp == forward_imp
时,会结束当前循环,或者当(curClass = curClass->superclass) == nil
时,也会将imp
赋值为forward_imp
跳出死循环
那么什么是forward_imp
呢?
在源码的第一行中其实就赋值了
const IMP forward_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_msgForward_impcache
中其实就是调用了__objc_msgForward
,而__objc_msgForward
中其实主要是调用了__objc_forward_handler
,那我们在全局搜索的时候,发现并没有找到对应的实现。
所以,根据我们第一步的流程,如果汇编找不到,就去找c++
的方法,去掉一个_
,即_objc_forward_handler
。
然后我们可以发现代码如下:
// Default forward handler halts the process.
__attribute__((noreturn, cold)) 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;
这就是我们平时经常看到的报错信息了。
2.5.2、resolveMethod_locked
if (slowpath(behavior & LOOKUP_RESOLVER)) {
behavior ^= LOOKUP_RESOLVER; // LOOKUP_RESOLVER = 2,
return resolveMethod_locked(inst, sel, cls, behavior);
}
上面的判断主要是控制当前的resolveMethod_locked
只会走一次
当上诉的死循环跳出来后,会走到当前方法resolveMethod_locked
。这就是我们常见的动态方法决议
。
resolveMethod_locked 分析
static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
runtimeLock.assertLocked();
ASSERT(cls->isRealized());
runtimeLock.unlock();
if (! cls->isMetaClass()) {
// try [cls resolveInstanceMethod:sel]
resolveInstanceMethod(inst, sel, cls);
}
else {
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
resolveClassMethod(inst, sel, cls);
if (!lookUpImpOrNil(inst, sel, cls)) {
resolveInstanceMethod(inst, sel, cls);
}
}
// chances are that calling the resolver have populated the cache
// so attempt using it
return lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE);
}
在该方法中,我们先分析不是元类的情况,即会调用resolveInstanceMethod
。
resolveInstanceMethod
源码实现如下:
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
SEL resolve_sel = @selector(resolveInstanceMethod:);
if (!lookUpImpOrNil(cls, resolve_sel, cls->ISA())) {
// Resolver not implemented.
return;
}
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(cls, resolve_sel, sel);
// Cache the result (good or bad) so the resolver doesn't fire next time.
// +resolveInstanceMethod adds to self a.k.a. cls
IMP imp = lookUpImpOrNil(inst, sel, cls);
if (resolved && PrintResolving) {
if (imp) {
_objc_inform("RESOLVE: method %c[%s %s] "
"dynamically resolved to %p",
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel), imp);
}
else {
// Method resolver didn't add anything?
_objc_inform("RESOLVE: +[%s resolveInstanceMethod:%s] returned YES"
", but no new implementation of %c[%s %s] was found",
cls->nameForLogging(), sel_getName(sel),
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel));
}
}
}
static inline IMP
lookUpImpOrNil(id obj, SEL sel, Class cls, int behavior = 0)
{
return lookUpImpOrForward(obj, sel, cls, behavior | LOOKUP_CACHE | LOOKUP_NIL);
}
- 1、在
resolveInstanceMethod
中,首先调用lookUpImpOrNil
判断是否实现了resolveInstanceMethod
,也就是我们说的动态方法决议
- 2、然后会调用
objc_msgSend
进行resolveInstanceMethod
的消息转发,并将未找到的sel
作为参数传出去 - 3、最后再次调用
lookUpImpOrNil
进行查询原来的方法是否有实现,也就是第二步是否有将imp
进行处理,重新走lookUpImpOrForward
流程。
2.6、log_and_fill_cache
主要是将整个方法加入到缓存中,也就是方便我们后续更快的查找方法。
整个流程如下:
log_and_fill_cache
-> cache_fill
-> cache->insert