快速查找
流程中哼勇,如果沒有
找到方法實(shí)現(xiàn)
都伪,無論是走到CheckMiss
還JumpMiss
,最終都會(huì)走到__objc_msgSend_uncached
匯編函數(shù)
- 在主流環(huán)境arm64環(huán)境為條件條件积担,在
objc-msg-arm64.s
文件中查找__objc_msgSend_uncached
的匯編實(shí)現(xiàn),核心是MethodTableLookup
猬仁,源碼如下
STATIC_ENTRY __objc_msgSend_uncached
UNWIND __objc_msgSend_uncached, FrameWithNoSaves
// THIS IS NOT A CALLABLE C FUNCTION
// Out-of-band p16 is the class to search
MethodTableLookup
TailCallFunctionPointer x17
END_ENTRY __objc_msgSend_uncached
- 搜索
MethodTableLookup
帝璧,核心是lookUpImpOrForward
,源碼如下:
.macro MethodTableLookup
stmfd sp!, {r0-r3,r7,lr}
add r7, sp, #16
sub sp, #8 // align stack
FP_SAVE
// lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER)
.if $0 == NORMAL
// receiver already in r0
// selector already in r1
.else
mov r0, r1 // receiver
mov r1, r2 // selector
.endif
mov r2, r9 // class to search
mov r3, #3 // LOOKUP_INITIALIZE | LOOKUP_INITIALIZE
blx _lookUpImpOrForward
mov r12, r0 // r12 = IMP
.if $0 == NORMAL
cmp r12, r12 // set eq for nonstret forwarding
.else
tst r12, r12 // set ne for stret forwarding
.endif
FP_RESTORE
add sp, #8 // align stack
ldmfd sp!, {r0-r3,r7,lr}
.endmacro
通過匯編進(jìn)行分析
-
在
main
函數(shù)[person say666];
中打斷點(diǎn)湿刽,通過Debug->Debug Worldflow->Always Show dissasembly
進(jìn)入?yún)R編的烁,如圖
image -
在
_objc_msgSend_uncached
加一個(gè)斷,controller+step into
進(jìn)入诈闺,如圖
image
最后走到的就是lookUpImpOrForward
渴庆,此時(shí)并不是匯編實(shí)現(xiàn)
C/C++
中調(diào)用 匯編 ,去查找匯編
時(shí)雅镊,C/C++
調(diào)用的方法需要多加
一個(gè)下劃線
匯編中調(diào)用C/C++
方法時(shí)襟雷,去查找C/C++
方法,需要將匯編調(diào)用的方法去掉
一個(gè)下劃線
慢速查找 - C/C++
-
匯編
部分的提示仁烹,全局搜索lookUpImpOrForward
耸弄,在objc-runtime-new.mm
文件中找到了源碼實(shí)現(xiàn),這是一個(gè)c
實(shí)現(xiàn)的函數(shù)
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 -- 表示類的迭代的上限
//(猜測(cè)這里遞歸的原因是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;
}
步驟如下:
【第一步】
cahce緩存
中進(jìn)行查找即快速查找
,找到并直接返回imp
变汪,反之則進(jìn)【第二部】-
【第二步】判斷
cls
- 是否
已知類
,有才能找到緩存
- 類是否
實(shí)現(xiàn)
侠坎,如果沒有,則需要先實(shí)現(xiàn)裙盾,確定其父類鏈实胸,此時(shí)實(shí)例化的目的是為了確定父類鏈、ro番官、以及rw等庐完,方法后續(xù)數(shù)據(jù)的讀取以及查找的循環(huán) - 是否
初始化
,如果沒有鲤拿,則callInitialize
初始化
- 是否
-
【第三步】
for循環(huán)
假褪,類繼承鏈 或者 元類繼承鏈
的順序查找- 當(dāng)前cls的方法列表中使用二分查找算法查找方法,如果找到近顷,則進(jìn)入
cache
寫入流程并返回imp
生音,如果沒有找到
,則返回nil
窒升,繼續(xù)循環(huán)
缀遍。 - 查找父類,如果沒有找饱须,imp置換成
forward_imp
域醇,進(jìn)行消息轉(zhuǎn)發(fā),并終止遞歸
進(jìn)入【第四步】
-
父類鏈
中存在循環(huán)蓉媳,則報(bào)錯(cuò)譬挚,終止循環(huán)
- 當(dāng)前cls的方法列表中使用二分查找算法查找方法,如果找到近顷,則進(jìn)入
-
【第四步】判斷是否執(zhí)行過動(dòng)態(tài)方法解析
- 如果
沒有
,執(zhí)行動(dòng)態(tài)方法解析
- 如果執(zhí)行過一次
動(dòng)態(tài)方法解析
酪呻,則走到消息轉(zhuǎn)發(fā)流程
- 如果
getMethodNoSuper_nolock方法:二分查找方法列表
二分查找的核心代碼
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; //key 等于 say666
uint32_t count;
//base相當(dāng)于low减宣,count是max,probe是middle玩荠,這就是二分
for (count = list->count; count != 0; count >>= 1) {
//從首地址+下標(biāo) --> 移動(dòng)到中間位置(count >> 1 左移1位即 count/2 = 4)
probe = base + (count >> 1);
uintptr_t probeValue = (uintptr_t)probe->name;
//如果查找的key的keyvalue等于中間位置(probe)的probeValue漆腌,則直接返回中間位置
if (keyValue == probeValue) {
// -- while 平移 -- 排除分類重名方法
while (probe > first && keyValue == (uintptr_t)probe[-1].name) {
//排除分類重名方法(方法的存儲(chǔ)是先存儲(chǔ)類方法贼邓,在存儲(chǔ)分類---按照先進(jìn)后出的原則,分類方法最先出闷尿,而我們要取的類方法盟猖,所以需要先排除分類方法)
//如果是兩個(gè)分類福侈,就看誰先進(jìn)行加載
probe--;
}
return (method_t *)probe;
}
//如果keyValue 大于 probeValue肖油,就往probe即中間位置的右邊查找
if (keyValue > probeValue) {
base = probe + 1;
count--;
}
}
return nil;
}
算法原理簡(jiǎn)述為:從第一次查找開始艰额,每次都取中間位置,與想查找的key的value值作比較劳景,如果相等绑咱,則需要排除分類方法,然后將查詢到的位置的方法實(shí)現(xiàn)返回枢泰,如果不相等,則需要繼續(xù)二分查找铝噩,如果循環(huán)至count = 0還是沒有找到衡蚂,則直接返回nil,如下所示:
動(dòng)態(tài)方法分析
動(dòng)態(tài)方法的源碼如下骏庸,在沒有找到實(shí)現(xiàn)的放方法時(shí)會(huì)崩潰毛甲,因此蘋果爸爸resolveInstanceMethod
通過這個(gè)方法再給一次機(jī)會(huì)進(jìn)行特殊處理。我們將在下一篇進(jìn)行詳細(xì)討論具被。
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(say666)) {
NSLog(@"%@ 來了",NSStringFromSelector(sel));
IMP imp = class_getMethodImplementation(self, @selector(sayMaster));
Method sayMMethod = class_getInstanceMethod(self, @selector(sayMaster));
const char *type = method_getTypeEncoding(sayMMethod);
return class_addMethod(self, sel, imp, type);
}
return [super resolveInstanceMethod:sel];
}