在上一篇文章中介紹了在匯編部分的緩存快速查找流程煤蹭。由于首次調(diào)用
或者緩存擴(kuò)容
等問題導(dǎo)致的緩存查找失敗钾军,就需要進(jìn)入慢速查找流程
.
objc_msgSend慢速查找
慢速查找入口-匯編部分
- 在快速查找流程無法找到對應(yīng)緩存的時候赎瞎,會跳到
CheckMiss\JumpMiss
這個macro中并且走到__objc_msgSend_uncached
這個函數(shù)中。
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 //跳轉(zhuǎn)
TailCallFunctionPointer x17 //寄存器調(diào)用
END_ENTRY __objc_msgSend_uncached
- 繼續(xù)探索
MethodTableLookup
的實(shí)現(xiàn)宾茂,發(fā)現(xiàn)實(shí)現(xiàn)很長,但是都是一些寄存器的操作等,只看我們關(guān)心的b链烈、bl
.macro MethodTableLookup
...
// 省略影響跟流程的代碼
bl _lookUpImpOrForward
...
- 繼續(xù)搜索
_lookUpImpOrForward
發(fā)現(xiàn)找不到對用的實(shí)現(xiàn)了。蘋果編碼有一個習(xí)慣通過前綴增加挚躯、減少_
,來實(shí)現(xiàn)多種語言的切換强衡。
通過調(diào)用lookUpImpOrForward
,就來到了c++部分码荔。
通過調(diào)試來跟蹤流程
前文中的流程跳轉(zhuǎn)有一部分猜測的成分漩勤,現(xiàn)在通過調(diào)試來驗(yàn)證一下之前的猜測.
-
打開斷點(diǎn)感挥,找到目標(biāo)調(diào)用
-
打開堆棧信息選項(xiàng)
-
按住control 點(diǎn)擊 Step + info
- 點(diǎn)擊下一步來到
__objc_msgSend_uncached
- 最終調(diào)用
lookUpImpOrForward
,而且給出了c++函數(shù)的位置
通過編譯調(diào)試進(jìn)一步驗(yàn)證了之前的猜測越败。
慢速查找c++流程
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
//參數(shù)準(zhǔn)備
const IMP forward_imp = (IMP)_objc_msgForward_impcache;
IMP imp = nil;
Class curClass;
//再進(jìn)行一次緩存的查找
if (fastpath(behavior & LOOKUP_CACHE)) {
imp = cache_getImp(cls, sel);
if (imp) goto done_nolock;
}
runtimeLock.lock(); //加鎖
// TODO: this check is quite costly during process startup.
//判斷該類是否是已知類
//這個操作需要從已知類表中做耗時的查找動作触幼;推測以后蘋果會優(yōu)化點(diǎn)
checkIsKnownClass(cls);
//判斷當(dāng)前類是否加載到內(nèi)存中(isa和rw信息是否存在)。
if (slowpath(!cls->isRealized())) {
//如果不存在進(jìn)行類的加載眉尸,并且會遞歸加載所有父類域蜗、元類。為了確定類的父類噪猾、isa鏈
cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
}
//判斷類是否初始化霉祸,同樣是遞歸判斷所有
if (slowpath((behavior & LOOKUP_INITIALIZE) && !cls->isInitialized())) {
//對類進(jìn)行初始化,并且會遞歸加載所有父類袱蜡、元類丝蹭。為了確定類的父類、isa鏈
cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
}
runtimeLock.assertLocked(); //解鎖
//循環(huán)查找坪蚁,無跳出條件
for (unsigned attempts = unreasonableClassCount();;) {
//使用二分法在當(dāng)前類的方法列表methodList中查找該方法
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
//本質(zhì)就是查找方法的imp實(shí)現(xiàn)
imp = meth->imp;
goto done;
}
//1. 將當(dāng)前類切換為父類
//2. 如果當(dāng)前類的父類是nil奔穿,也就是在NSObject中都沒有找到則直接跳出循環(huán)
if (slowpath((curClass = curClass->superclass) == nil)) {
imp = forward_imp;
break;
}
// 如果父類鏈中存在循環(huán),則停止
if (slowpath(--attempts == 0)) {
_objc_fatal("Memory corruption in class list.");
}
//對父類進(jìn)行方法查找
//1.匯編緩存快速查找
imp = cache_getImp(curClass, sel);
//如果父類沒找到,首次會進(jìn)入動態(tài)方法解析
if (slowpath(imp == forward_imp)) {
break;
}
//在父類中找到了該方法
if (fastpath(imp)) {
goto done;
}
}
//每個類首次都會進(jìn)行一次方法動態(tài)解析
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;
}
//返回查詢結(jié)果imp
return imp;
這就是整個消息imp查詢閉環(huán)流程(快贱田、慢)。
慢速查找流程圖
objc_msgSend慢速流程.png
cache_getImp沒有發(fā)生遞歸
STATIC_ENTRY _cache_getImp
GetClassFromIsa_p16 p0
CacheLookup GETIMP, _cache_getImp
.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
- 可以看到CacheLookup的第一個參數(shù)是
GETIMP
- GETIMP下并沒有調(diào)用
__objc_msgSend_uncached
嘴脾,所以并不會產(chǎn)生遞歸男摧,只會查找當(dāng)前類的緩存
方法沒有找到,而且沒有實(shí)現(xiàn)方法解析
extern void _objc_msgForward_impcache(void);看到extern
就知道它又是匯編代碼編寫的了
STATIC_ENTRY __objc_msgForward_impcache
//跳轉(zhuǎn)到 __objc_msgForward
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
沒有找到下文了译打,猜測它是c++編寫耗拓,所以取一個_繼續(xù)搜索
- 相信這段話已經(jīng)很熟悉了吧?平時開發(fā)應(yīng)該見過無數(shù)次了奏司。
MethodList(有序數(shù)組)二分查找
以上已經(jīng)解釋了慢速查找的整個流程乔询,現(xiàn)在對MethodList二分查找
的實(shí)現(xiàn)做一個解釋。(核心方法findMethodInSortedMethodList
)
ALWAYS_INLINE static method_t *
findMethodInSortedMethodList(SEL key, const method_list_t *list)
{
ASSERT(list);
//當(dāng)前l(fā)ist是有序的韵洋,在類創(chuàng)建的時候就已經(jīng)進(jìn)行了加載
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) --> 移動到中間位置(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) {
//找出分類重名方法
//如果是兩個分類,就看誰先進(jìn)行加載
probe--;
}
return (method_t *)probe;
}
//如果keyValue 大于 probeValue勉吻,就往probe即中間位置的右邊查找
if (keyValue > probeValue) {
base = probe + 1;
count--;
}
}
return nil;
}
示例圖:
1. 向左查詢
8中找1
2. 向右查詢
8中找7