前言
之前有講到過伞插,OC當(dāng)中幾乎所有的方法調(diào)用到底層都是消息的發(fā)送 objc_msgSend 掠廓,也探討了如何從cache 中通過 cmd 獲取 imp 這些流程羽历。但是當(dāng)我們從緩存當(dāng)找不到 imp 的時候又該怎么辦呢榜苫?
前面我們在class 的探討中提到過 bits 當(dāng)中存儲量很多的屬性铝宵、方法等谦纱。
前面我們在 objc_msgSend() 的查找過程中提到 objc_msgSend_uncached 這個方法看成,這個方法表示緩存查找不到,那么我們就從 objc_msgSend_uncached 這個方法入手跨嘉。
匯編緩存找不到
通過搜索 __objc_msgSend_uncached 我們可以找到 MethodTableLookup 才是關(guān)鍵
于是我們?nèi)フ?MethodTableLookup 這個方法川慌,在這個方法里面我們最終找到了由 C++ 實現(xiàn)的 lookUpImpOrForward 方法。
.macro MethodTableLookup
SAVE_REGS MSGSEND
// lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER)
// receiver and selector already in x0 and x1
mov x2, x16 //賦值祠乃,把x16( cls ) 賦值給 x2
mov x3, #3 // x3 = 3
bl _lookUpImpOrForward
// IMP in x0
mov x17, x0
// 由于這里吧 imp 放在了x0里面梦重,所以x0 應(yīng)該就是上一個 跳轉(zhuǎn)的事件的返回值。所以我們的直接就去尋找 _lookUpImpOrForward亮瓷,
// 但是全局搜索 _lookUpImpOrForward 過程中發(fā)現(xiàn)沒有琴拧,于是乎我們就搜索 lookUpImpOrForward
// 發(fā)現(xiàn)在 objc-runtime-new 里面定義了 lookUpImpOrForward 這個方法。
RESTORE_REGS MSGSEND
.endmacro
這里也說明了嘱支,我們 objc_msgSend 發(fā)送消息通過 cmd 獲取 imp 的過程是先用匯編在 cache 里面查找蚓胸,如果找不到了又會采用 C++ 繼續(xù)查找挣饥。
接下來我們?nèi)タ?lookUpImpOrForward 這個過程。
通過整個方法結(jié)果看到最后是一個 return imp; 的過程沛膳,所以我們的整個流程就是去查找 imp 什么時候賦值的扔枫。于是有了下面這段代碼
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
......
/// 這里是個死循環(huán) for (<#initialization#>; <#condition#>; <#increment#>) 通過這個定義可以知道, condition 沒有锹安,也就是沒有終止條件短荐。
for (unsigned attempts = unreasonableClassCount();;) {
/// isConstantOptimizedCache 這個方法從其具體的定義和實現(xiàn)的判斷(objc_cache.mm 里面)只有在真機的環(huán)境下才會有可能存在。
if (curClass->cache.isConstantOptimizedCache(/* strict */true)) {
#if CONFIG_USE_PREOPT_CACHES
/// 這里也印證了只有真機的環(huán)境才需要這樣做叹哭。有可能在查找的過程中有了緩存忍宋,那么直接返回。
imp = cache_getImp(curClass, sel);
if (imp) goto done_unlock;
curClass = curClass->cache.preoptFallbackClass();
#endif
} else {
// curClass method list.
Method meth = getMethodNoSuper_nolock(curClass, sel);
/// 這里才是真正的去獲取 imp的流程
if (meth) {
imp = meth->imp(false);
goto done; /// 獲取到了imp 直接 goto done
}
if (slowpath((curClass = curClass->getSuperclass()) == 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.
imp = cache_getImp(curClass, sel);
/// 如果還是獲取不到那么就去從父類厘米獲取风罩,然后又走cache 那套流程這樣形成一個遞歸像上的流程糠排。
/// 如果一旦獲取到了,那么應(yīng)該走上面的 goto done泊交, 下面的 goto done 判斷應(yīng)該只是一個放置意外吧乳讥。
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;
}
}
......
return imp;
}
通過上面的分析柱查,我們最終找打了 getMethodNoSuper_nolock -> search_method_list_inline 這個方法廓俭, 最后找到了 findMethodInSortedMethodList 這個方法。findMethodInSortedMethodList 這個方法就是我們的查找流程唉工,由于是排好序的研乒,所以按照最優(yōu)解就是二分查找法。于是就到了我們二分查找法去查找 imp 的過程了淋硝。
慢速查找流程
是應(yīng)該去找父類還是去找方法列表雹熬。
在整個查找的主線中有點小問題
二分查找
上面最終找到了 findMethodInSortedMethodList 這個方法,那么我們?nèi)タ纯?findMethodInSortedMethodList 的實現(xiàn)谣膳。
template<class getNameFunc>
ALWAYS_INLINE static method_t *
findMethodInSortedMethodList(SEL key, const method_list_t *list, const getNameFunc &getName)
{
ASSERT(list);
auto first = list->begin(); /// 獲取第一個
auto base = first;
decltype(first) probe; /// 這個是我們想要返回的結(jié)果竿报。
uintptr_t keyValue = (uintptr_t)key;
uint32_t count;
/**
假如我們一共有 8 個方法,即 count = 8 , 那么 probe 就應(yīng)該是從 0 ~7 當(dāng)中继谚,假定結(jié)果為7烈菌,key = 7,最后一個
即:base = 0花履, probe = 0芽世,key = 7, count = 8
那么第一次for 循環(huán)相當(dāng)于
for (count = 8, count !=0, count = 4) count >>= 1 -> count = count >> 1 -> 1000 >> 1 = 0100 = 4
count = 8, base = 0;
probe = 0 + 4 = 4;
4 != 7 所以查找失敗
由于 7 > 4 ,所以 base = 5 诡壁, count = 3
進入第二次循環(huán)
coun = 3, base = 5
probe = base + count >> 1 = 5 + 1 = 6;
由于 6 != 7
所以沒查找失敗
由于 7 > 6 , 所以 count = 2-1 = 1, base = probe + 1 = 6 + 1 = 7
第三次循環(huán)
count = 1, base = 7
probe = base + count >> 1 = 7+0 = 7
由于7 == 7 济瓢,所以查找成功
*/
for (count = list->count; count != 0; count >>= 1) {
probe = base + (count >> 1);
/// 獲取probe 的name ,其實就是 SEL
uintptr_t probeValue = (uintptr_t)getName(probe);
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)getName((probe - 1))) {
/// 這里就是 category 的同名方法是怎么存的就怎么取妹卿。也就是最后編譯的同名方法在前面旺矾,這個就是循環(huán)取最前面的一個蔑鹦。
probe--;
}
/// 從結(jié)果看,這里是有這個才是有用的返回箕宙,那么結(jié)果必然會到這里举反。
return &*probe;
}
//如果沒有找到, 并且value 大于當(dāng)前查找的 key扒吁,說明在后面火鼻,那么下次的循環(huán)就變成
if (keyValue > probeValue) {
base = probe + 1;
count--;
}
}
return nil;
}
同理,比如我們要找小端位置的結(jié)果呢雕崩?如下所示魁索。
如果我們查找小的呢,比如0號位置盼铁。
base = 0粗蔚, probe = 0,key = 0饶火, list.count = 8
那么第一次為
for (8, 8 != 0, 8 >>= 1)
probe = base + count >> 1 = 0 + 4
因為 4 != 0 鹏控,所以查找失敗
由于 0 > 4 不成立,所以 base 和 count 不變
第二次循環(huán)
count = 4, base = 0
probr = base + count >> 1 = 0 + 2 = 2
因為 2 != 0 所以查找失敗
由于 0 > 2 不成立肤寝, 所以 base 和 count 不變
第三次
count = 2, base = 0
probr = base + count >> 1 = 0 + 1 = 1
因為 1 != 0 所以查找失敗
由于 0 > 1 不成立当辐, 所以 base 和 count 不變
第四次
count = 1, base = 0
probr = base + count >> 1 = 0 + 0 = 0
因為 0 == 0 ,所以查找成功鲤看,返回
通過上面的結(jié)果缘揪,我們找到了有可能找到了imp, 但是如果沒有找到呢义桂?
看看幾個判斷.
- 1找筝、判斷 getSuperclass 是否為空,如果為空慷吊,那么就執(zhí)行 forward_imp袖裕。
if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
// No implementation found, and method resolver didn't help.
// Use forwarding.
imp = forward_imp;
break;
}
- 2、imp = cache_getImp(curClass, sel); 遞歸像父類查找
到此處溉瓶,如果imp 存在急鳄,那么我們一定可以找到,然后執(zhí)行 goto done;
done:
if (fastpath((behavior & LOOKUP_NOCACHE) == 0)) {
#if CONFIG_USE_PREOPT_CACHES
while (cls->cache.isConstantOptimizedCache(/* strict */true)) {
cls = cls->cache.preoptFallbackClass();
}
#endif
log_and_fill_cache(cls, imp, sel, inst, curClass);
}
log_and_fill_cache 這個最后又會執(zhí)行 cache.insert
總結(jié):
這里需要一張流程圖