慢速查找匯編分析
在上篇文章iOS底層 - objc_msgSend快速查找流程分析中我們分析了通過匯編進(jìn)行的objc_msgSend快速查找流程:CacheLookup 匯編方法在cache緩存中沒找到,CheckMiss 和 JumpMiss 都會跳到__objc_msgSend_uncached 匯編方法。
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查找方法列表函數(shù)。
.macro MethodTableLookup
// push frame
SignLR
stp fp, lr, [sp, #-16]!
mov fp, sp
// save parameter registers: x0..x8, q0..q7
sub sp, sp, #(10*8 + 8*16)
stp q0, q1, [sp, #(0*16)]
stp q2, q3, [sp, #(2*16)]
stp q4, q5, [sp, #(4*16)]
stp q6, q7, [sp, #(6*16)]
stp x0, x1, [sp, #(8*16+0*8)]
stp x2, x3, [sp, #(8*16+2*8)]
stp x4, x5, [sp, #(8*16+4*8)]
stp x6, x7, [sp, #(8*16+6*8)]
str x8, [sp, #(8*16+8*8)]
// lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER)
// receiver and selector already in x0 and x1
mov x2, x16
mov x3, #3
bl _lookUpImpOrForward // 跳轉(zhuǎn)到此方法
// IMP in x0
mov x17, x0
// restore registers and return
ldp q0, q1, [sp, #(0*16)]
ldp q2, q3, [sp, #(2*16)]
ldp q4, q5, [sp, #(4*16)]
ldp q6, q7, [sp, #(6*16)]
ldp x0, x1, [sp, #(8*16+0*8)]
ldp x2, x3, [sp, #(8*16+2*8)]
ldp x4, x5, [sp, #(8*16+4*8)]
ldp x6, x7, [sp, #(8*16+6*8)]
ldr x8, [sp, #(8*16+8*8)]
mov sp, fp
ldp fp, lr, [sp], #16
AuthenticateLR
.endmacro
在MethodTableLookup中經(jīng)過一系列準(zhǔn)備工作,將會跳轉(zhuǎn)到_lookUpImpOrForward方法,全局查找_lookUpImpOrForward方法,但未找到,那么我們猜想一下是否_lookUpImpOrForward方法就不在匯編中,而是在C/C++中?
驗證
創(chuàng)建繼承自NSObject的類LGPerson,并添加方法sayHello,main.m文件中創(chuàng)建實例并調(diào)用方法:
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
LGPerson *person = [LGPerson alloc];
1 [person sayHello];
}
return 0;
}
在調(diào)用sayHello方法處打下斷點,打開Debug--->Debug Workflow --->Always show Disassembly,程序來到底層匯編中:

在圖中16行
objc_msgSend方法處打下斷點,繼續(xù)下一步,程序停住,按下control + step into ,進(jìn)入到objc_msgSend 匯編方法中:
發(fā)現(xiàn)此時
objc_msgSend方法最后也跳轉(zhuǎn)到了_objc_msgSend_uncached 中,對我們上面的匯編代碼在cache中沒找到跳轉(zhuǎn)到_objc_msgSend_uncached方法一樣。接著打下斷點,繼續(xù)讓程序進(jìn)入
_objc_msgSend_uncached 方法匯編中:
我們看到代碼最終會跳到方法
lookUpImpOrForward中,在C++文件 objc-runtime-new.mm:6099 處。
慢速查找C/C++分析
全局搜索lookUpImpOrForward ,找到objc-runtime-new.mm 文件中對應(yīng)的C代碼:
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
// 考慮多線程過程影響,如果別的線程緩存了,那么直接利用cache_getImp匯編方法快速查找,CacheLookup GETIMP, _cache_getImp
// 找到了就直接返回imp, 沒找到繼續(xù)步驟
if (fastpath(behavior & LOOKUP_CACHE)) {
imp = cache_getImp(cls, sel);
if (imp) goto done_nolock;
}
runtimeLock.lock();
//是否是已經(jīng)存在的類,是否是已經(jīng)加載到內(nèi)存中的類,是的話才能繼續(xù)下面查找工作
checkIsKnownClass(cls);
// 類是否實現(xiàn)了,配置類的繼承鏈及元類鏈,為后續(xù)查找工作做好了準(zhǔn)備(雙向鏈表的結(jié)構(gòu))
if (slowpath(!cls->isRealized())) {
cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
}
// 是否類已經(jīng)初始化過了,進(jìn)行初始化
if (slowpath((behavior & LOOKUP_INITIALIZE) && !cls->isInitialized())) {
cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
}
runtimeLock.assertLocked();
curClass = cls;
// 無限循環(huán)方法 當(dāng)imp = forward_imp時 break 退出循環(huán)
for (unsigned attempts = unreasonableClassCount();;) {
// curClass method list.
// 最終使用二分法 查找當(dāng)前 curClass 類中是否有對應(yīng)的imp
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
imp = meth->imp;
goto done;
}
// 當(dāng)前類方法列表沒找到,將當(dāng)前類變?yōu)楫?dāng)前類的父類,如果父類為nil,則imp賦值forward_imp退出此for循環(huán)
if (slowpath((curClass = curClass->superclass) == 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.
// 優(yōu)先通過cache_getImp 匯編查找父類緩存,如果找到直接下面的go done
// 如果沒有找到,那么下一個循環(huán),找父類的方法列表,然后父類的父類的緩存,方法列表,直到最后父類為nil 退出循環(huán)。
imp = cache_getImp(curClass, sel);
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;
}
if (fastpath(imp)) {
// Found the method in a superclass. Cache it in this class.
goto done;
}
}
// No implementation found. Try method resolver once.
// 到這里 說明沒找到方法,最后 給一次方法解析機會
if (slowpath(behavior & LOOKUP_RESOLVER)) {
//動態(tài)方法決議的控制條件,表示流程只走一次
behavior ^= LOOKUP_RESOLVER;
return resolveMethod_locked(inst, sel, cls, behavior);
}
done:
// 將找到的sel/imp信息緩存進(jìn)cache
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;
}
其中,getMethodNoSuper_nolock 查找當(dāng)前類中是否有sel所對應(yīng)的Method(method_t結(jié)構(gòu)體),
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;
/** 舉例講解二分法 方(法列表從前往后 ,從大到小的順序排列)
// 0x01 0x02 0x03 0x04 0x05 0x05 0x06 0x07 0x08
// keyValue = 0x02
// base = 0x01
// probe = 0x05 --> 0x03 --> 0x02
// 1 count 8 >> 1 = 4
// 2 count 4 >> 1 = 2
// 3 count 2 >> 1 = 1 此時probe = 0x02 即找到了
// keyValue = 0x07
// base = 0x01 --> 0x06
// probe = 0x05 --> 0x07
// 1 count 8 >> 1 = 4
// 2 count 7 >> 1 = 3 此時probe = 0x07 即找到了
*/
for (count = list->count; count != 0; count >>= 1) {
probe = base + (count >> 1);
uintptr_t probeValue = (uintptr_t)probe->name;
if (keyValue == probeValue) {
// `probe` is a match.
// Rewind looking for the *first* occurrence of this value.
// This is required for correct category overrides.
// 此處還要考慮前面有沒有相同方法,因為考慮分類 ,分類的方法在最前面
while (probe > first && keyValue == (uintptr_t)probe[-1].name) {
probe--;
}
return (method_t *)probe;
}
if (keyValue > probeValue) {
base = probe + 1;
count--;
}
}
return nil;
}
cache_getImp 方法解析
objc_msg_arm64.s 文件中cache_getimp 匯編代碼如下:
STATIC_ENTRY _cache_getImp
GetClassFromIsa_p16 p0
// 所帶的$0 與 objc_msgSend (NORMAL) 不一樣
CacheLookup GETIMP, _cache_getImp
LGetImpMiss:
mov p0, #0
ret
END_ENTRY _cache_getImp
發(fā)現(xiàn)匯編走的方法也是CacheLookup 方法 ,快速方法查找父類的緩存里是否有此方法的緩存。
總結(jié)
- 所有
實例方法的實現(xiàn)查找路徑為:類-->父類-->NSObject-->nil - 所有
類方法的實現(xiàn)查找路徑為:元類-->根元類-->NSObject-->nil - 如果在上方兩條方法尋找鏈中都
未找到,則會嘗試動態(tài)方法決議,最后給一次機會。 - 如果動態(tài)方法決議
失敗,那么會觸發(fā)消息轉(zhuǎn)發(fā),尋找實現(xiàn)了此方法的別的類。