上一篇中,分析了快速查找流程,如果快速查不到,則需要進(jìn)入慢速查找流程,核心方法_lookUpImpOrForward。
慢速查找底層源碼
_lookUpImpOrForward慢速查找流程
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
// 定義的消息轉(zhuǎn)發(fā)
const IMP forward_imp = (IMP)_objc_msgForward_impcache;
IMP imp = nil;
Class curClass;
runtimeLock.assertUnlocked();
// 快速查找,如果找到則直接返回imp
//目的:防止多線程操作時(shí),剛好調(diào)用函數(shù),此時(shí)緩存進(jìn)來了
if (fastpath(behavior & LOOKUP_CACHE)) {
imp = cache_getImp(cls, sel);
if (imp) goto done_nolock;
}
//加鎖,目的是保證讀取的線程安全
runtimeLock.lock();
//判斷是否是一個(gè)已知的類:判斷當(dāng)前類是否是已經(jīng)被認(rèn)可的類,即已經(jīng)加載的類
checkIsKnownClass(cls);
//判斷類是否實(shí)現(xiàn),如果沒有,需要先實(shí)現(xiàn),此時(shí)的目的是為了確定父類鏈,方法后續(xù)的循環(huán)
if (slowpath(!cls->isRealized())) {
cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
}
//判斷類是否初始化,如果沒有,需要先初始化
if (slowpath((behavior & LOOKUP_INITIALIZE) && !cls->isInitialized())) {
cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
}
runtimeLock.assertLocked();
curClass = cls;
//----查找類的緩存
// unreasonableClassCount -- 表示類的迭代的上限
//(猜測這里遞歸的原因是attempts在第一次循環(huán)時(shí)作了減一操作,然后再次循環(huán)時(shí),仍在上限的范圍內(nèi),所以可以繼續(xù)遞歸)
for (unsigned attempts = unreasonableClassCount();;) {
//---當(dāng)前類方法列表(采用二分查找算法),如果找到,則返回,將方法緩存到cache中
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
imp = meth->imp;
goto done;
}
//當(dāng)前類 = 當(dāng)前類的父類,并判斷父類是否為nil
if (slowpath((curClass = curClass->superclass) == nil)) {
//--未找到方法實(shí)現(xiàn),方法解析器也不行,使用轉(zhuǎn)發(fā)
imp = forward_imp;
break;
}
// 如果父類鏈中存在循環(huán),則停止
if (slowpath(--attempts == 0)) {
_objc_fatal("Memory corruption in class list.");
}
// --父類緩存
imp = cache_getImp(curClass, sel);
if (slowpath(imp == forward_imp)) {
// 如果在父類中找到了forward,則停止查找,且不緩存,首先調(diào)用此類的方法解析器
break;
}
if (fastpath(imp)) {
//如果在父類中,找到了此方法,將其存儲(chǔ)到cache中
goto done;
}
}
//沒有找到方法實(shí)現(xiàn),嘗試一次方法解析
if (slowpath(behavior & LOOKUP_RESOLVER)) {
//動(dòng)態(tài)方法決議的控制條件,表示流程只走一次
behavior ^= LOOKUP_RESOLVER;
return resolveMethod_locked(inst, sel, cls, behavior);
}
done:
//存儲(chǔ)到緩存
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;
}
我們來分析下整個(gè)慢速查找流程:
首先在cache緩存中進(jìn)行查找,即快速查找,找到則直接返回imp。
接下來判讀cls,先判斷是否是已知類,如果不是則報(bào)錯(cuò),再判斷類是否實(shí)現(xiàn),如果沒有,則需要先實(shí)現(xiàn),確定其父親鏈,最后判斷是否初始化,如果沒有,則初始化。
判斷完成后,就會(huì)開啟一個(gè)for循環(huán),通過方法名和當(dāng)前類名去對(duì)應(yīng)的方法列表查詢。這是一個(gè)死循環(huán),循環(huán)退出的條件就是(curClass->superclass) == nil。每次循環(huán)如果沒有結(jié)果curClass的指針就會(huì)指向父類,直到為nil跳出循環(huán)。
其中使用了一個(gè)二分查找算法,如果查找到,則進(jìn)入cache寫入流程,并返回imp,否則返回nil。然后是多個(gè)判斷,第一個(gè)判斷是將當(dāng)前cls賦值為父類,判斷如果父類等于nil,則終止遞歸,進(jìn)入消息轉(zhuǎn)發(fā)。第二個(gè)判斷如果父類鏈中存在循環(huán),也報(bào)錯(cuò),終止循環(huán)。上述條件全都滿足時(shí),就會(huì)在父類緩存中查找方法,如果未找到,則直接返回nil,繼續(xù)循環(huán)查找,如果找到,則直接返回imp,執(zhí)行cache寫入流程。
最后判斷是否執(zhí)行過動(dòng)態(tài)方法解析,如果沒有,執(zhí)行動(dòng)態(tài)方法解析,如果執(zhí)行過一次動(dòng)態(tài)方法解析,則走到消息轉(zhuǎn)發(fā)流程。
以上就是整個(gè)慢速查找流程。
getMethodNoSuper_nolock:二分查找方法列表
核心源碼
ALWAYS_INLINE static method_t *
findMethodInSortedMethodList(SEL key, const method_list_t *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;
}
具體可分為以下幾步
- base指向列表第一個(gè)元素。
- 獲取列表中間元素probe,將這個(gè)元素的方法名并轉(zhuǎn)換成uintptr_t類型。比較keyValue和probeValue的大小。
- 如果相等,說明找到了對(duì)應(yīng)的方法,然后進(jìn)一步循環(huán)判斷這個(gè)位置的方法的SEL是否和前一個(gè)位置方法的SEL相等,相等則可能是有分類的同名方法,指針前移,然后循環(huán),直到離開循環(huán)條件,返回結(jié)果。
- 如果keyValue大于probeValue的值,base = probe + 1使其重新指向減半后的列表第一個(gè)元素,并且count --,然后重復(fù)步驟2.
- 如果keyValue小于probeValue的值,重復(fù)步驟2.
當(dāng)完成了方法的查找之后,就會(huì)直接goto done,這一步會(huì)對(duì)查找到的方法做一個(gè)方法緩存的操作,內(nèi)部會(huì)調(diào)用cache_t::insert(),方便下次調(diào)用的時(shí)候,消息發(fā)送就會(huì)通過緩存查找,走快速查找流程。
當(dāng)getMethodNoSuper_nolock()沒有在自己類中找到方法的時(shí)候
就會(huì)調(diào)用if (slowpath((curClass = curClass->superclass) == nil))
把當(dāng)前類指針curClass指向自己的父類,然開始從父類緩存中開始查找cache_getImp(),如果找到了就返回,否則就會(huì)回到頂部遞歸循環(huán)。如果一直沒有查詢到結(jié)果則會(huì)進(jìn)入下一個(gè)流程——消息的動(dòng)態(tài)轉(zhuǎn)發(fā)。
總的來說,二分查找算法的核心思想,是從第一次查找開始,每次都取中間位置,與想查找的key的value值作比較,如果相等,則需要排除分類方法,然后將查詢到的位置的方法實(shí)現(xiàn)返回,如果不相等,則需要繼續(xù)二分查找,如果循環(huán)至count = 0還是沒有找到,則直接返回nil。