在iOS底層之objc_msgSend快速查找流程里分析里調(diào)用方法的本質(zhì)乃坤,就是消息發(fā)送矢劲,查找類的方法緩存邀杏,那么如果經(jīng)歷CacheLookup
后沒找到緩存驰徊,即快速查找流程找不到庶诡,則會開始慢速查找惦银,從methodList
查找,這一篇文章我們來分析方法的查找流程末誓。
在CacheLookup
快速查找流程中扯俱,當(dāng)沒有找到方法imp
緩存,無論是走到CheckMiss
還是JumpMiss
喇澡,最終都會走到__objc_msgSend_uncached
匯編函數(shù)迅栅。其定義是:
STATIC_ENTRY __objc_msgSend_uncached
UNWIND __objc_msgSend_uncached, FrameWithNoSaves
// THIS IS NOT A CALLABLE C FUNCTION
// Out-of-band r10 is the searched class
// r10 is already the class to search
MethodTableLookup NORMAL // r11 = IMP
jmp *%r11 // goto *imp
END_ENTRY __objc_msgSend_uncached
可以看到關(guān)鍵代碼MethodTableLookup
,會查找方法表晴玖。
其定義是:
.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
// 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
其中關(guān)鍵代碼是_lookUpImpOrForward
读存。為什么为流?
通過調(diào)用一個實(shí)例方法[person sayHello]
,并打斷點(diǎn)让簿。打開匯編調(diào)試查看:
可以看到匯編停在了
[person sayHello]
調(diào)用之前敬察,下一步開始發(fā)送消息調(diào)用sayHello
。打斷點(diǎn)objc_msgSend
拜英,按住control+點(diǎn)擊step into
進(jìn)入內(nèi)部調(diào)用静汤。_objc_msgSend_uncached
之上完成了類信息獲取,方法快速查找流程居凶,沒找到緩存時來到了_objc_msgSend_uncached
虫给,斷點(diǎn)跳到這一句,繼續(xù)step into
進(jìn)入內(nèi)部侠碧。可以看到來到了lookUpImpOrForward函數(shù)抹估,顯示這個函數(shù)在
objc-runtime-new.mm
文件的6099
行。也就驗(yàn)證了上面我們說的
_objc_msgSend_uncached
之后會來到_lookUpImpOrForward
弄兜。
通過匯編源碼查找
C/C++
源碼的技巧
例如_lookUpImpOrForward
,
匯編中查找C/C++
方法時药蜻,需要將匯編調(diào)用的方法_lookUpImpOrForward
去掉一個下劃線
從_objc_msgSend_uncached過來時behavior 是LOOKUP_INITIALIZE | LOOKUP_RESOLVER,該枚舉定義是
/* method lookup */
enum {
LOOKUP_INITIALIZE = 1,
LOOKUP_RESOLVER = 2,
LOOKUP_CACHE = 4,
LOOKUP_NIL = 8,
};
搜索找到lookUpImpOrForward
的定義替饿,主要的步驟分析我都寫在注釋里了语泽。
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
//_objc_msgForward_impcache獲取消息轉(zhuǎn)發(fā)的函數(shù)地址,用于方法未找到并且動態(tài)方法決議未處理時調(diào)用视卢。如果消息轉(zhuǎn)發(fā)也不成功踱卵,則程序會拋出方法未識別的錯誤
const IMP forward_imp = (IMP)_objc_msgForward_impcache;
IMP imp = nil;
Class curClass;
runtimeLock.assertUnlocked();
// Optimistic cache lookup
//當(dāng)behavior&LOOKUP_CACHE==1時,查找下緩存据过。為了防止多線程操作時惋砂,調(diào)用函數(shù)時,此時另一線程緩存進(jìn)來了绳锅,可以快速查找一遍緩存西饵,因?yàn)橄旅娴穆俨檎液芎臅r
//1.當(dāng)從MethodTableLookup過來時,behavior=3鳞芙,3&4=0為假眷柔,往下執(zhí)行
//2.動態(tài)方法決議回來第一次時'sel=resolveInstanceMethod',此時behavior=12 12&4=4為真原朝,這時還未緩存resolveInstanceMethod闯割,imp為空,往下執(zhí)行)
//3. 動態(tài)方法決議回來第二次時'sel=say666',此時behavior=12竿拆, 12&4=4為真,無論動態(tài)方法決議流程+resolveInstanceMethod里是否正確添加了imp宾尚,此時緩存中依舊為空丙笋,往下執(zhí)行
//4.動態(tài)方法決議處理后谢澈,會從resolveMethod_locked的lookUpImpOrForward重新進(jìn)來一次,此時behavior=5御板,5&4=4為真锥忿,此時找到的imp為動態(tài)方法決議第二次進(jìn)來時緩存的say666的方法地址,跳轉(zhuǎn)done_nolock(不緩存)
if (fastpath(behavior & LOOKUP_CACHE)) {
imp = cache_getImp(cls, sel);
if (imp) goto done_nolock;
}
//在isRealized和isInitialized檢查期間保持鎖怠肋,防止對并發(fā)實(shí)現(xiàn)的競爭
//runtimeLock在方法搜索過程中被保持
//方法查找+緩存填充相對于方法添加而言是原子的
//保證方法查找過程中method-lookup + cache-fill中方法添加的原子性敬鬓。
// Otherwise, a category could be added but ignored indefinitely because
// the cache was re-filled with the old value after the cache flush on
// behalf of the category.
runtimeLock.lock();
// TODO: this check is quite costly during process startup.
//檢查是否是已知類(已經(jīng)加載的類)
//確保這個類是通過objc_duplicateClass, objc_initializeClassPair或objc_allocateClassPair合法注冊的,或者內(nèi)置到二進(jìn)制文件中的笙各,而不是創(chuàng)建一個看起來像類而實(shí)際并不是的二進(jìn)制的blob钉答,做CFI攻擊
checkIsKnownClass(cls);
//小概率情況下,當(dāng)前類未實(shí)現(xiàn)時
//如果沒有杈抢,需要先實(shí)現(xiàn)数尿,目的是為了能確定父類繼承鏈和元類繼承鏈,后面查找遞歸惶楼,當(dāng)前類沒找到右蹦,則需要從繼承鏈查找
//內(nèi)部有雙向鏈表會更新子類和父類
//還會把類的相關(guān)屬性協(xié)議方法貼到rw.ext()中
if (slowpath(!cls->isRealized())) {
cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
// runtimeLock may have been dropped but is now locked again
}
//判斷如果類沒有初始化,則會遞歸父類繼承鏈執(zhí)行初始化所有的類
//沒實(shí)現(xiàn)時會callInitialize對類發(fā)送消息調(diào)用initialize方法
//所以側(cè)面說明了initialize和load方法一樣歼捐,不需要主動調(diào)用何陆,底層實(shí)現(xiàn)時幫你調(diào)用
if (slowpath((behavior & LOOKUP_INITIALIZE) && !cls->isInitialized())) {
cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
// runtimeLock may have been dropped but is now locked again
// If sel == initialize, class_initialize will send +initialize and
// then the messenger will send +initialize again after this
// procedure finishes. Of course, if this is not being called
// from the messenger then it won't happen. 2778172
}
runtimeLock.assertLocked();
curClass = cls;
// The code used to lookpu the class's cache again right after
// we take the lock but for the vast majority of the cases
// evidence shows this is a miss most of the time, hence a time loss.
//
// The only codepath calling into this without having performed some
// kind of cache lookup is class_getInstanceMethod().
//重點(diǎn)!1ⅰ4ぁ!從這里對imp賦值
for (unsigned attempts = unreasonableClassCount();;) {
// curClass method list.
//查找curClass方法列表颂翼,如果有晃洒,就不需要去找curClass的父類了
//1.當(dāng)動態(tài)方法決議回來第一次時'sel=resolveInstanceMethod',如果curClass(或者遞歸的父類鏈)方法列表實(shí)現(xiàn)了朦乏,則賦值imp球及,執(zhí)行done
//2.當(dāng)動態(tài)方法決議回來第二次時'sel=say666',此時curClass的方法列表可以找到imp呻疹,跳到done
Method meth = getMethodNoSuper_nolock(curClass, sel);
//如果找到了吃引,則跳轉(zhuǎn)到done(緩存方法、解鎖)
if (meth) {
imp = meth->imp;
goto done;
}
//本類沒找到刽锤,則將curClass賦值為當(dāng)前類的父類
//如果父類是nil镊尺,也就是繼承鏈走完了,那么會imp = forward_imp走消息轉(zhuǎn)發(fā)并思,跳出循環(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.
//如果父類繼承鏈中存在環(huán)庐氮,就停止
if (slowpath(--attempts == 0)) {
_objc_fatal("Memory corruption in class list.");
}
// Superclass cache.
//查找當(dāng)前類的緩存,注意宋彼,上面已經(jīng)將curClass父類賦值給當(dāng)前類弄砍,所以是查找父類的緩存仙畦,并不是遞歸當(dāng)前類了
//查找過程是`cache_getImp` - `lookup` - `lookUpImpOrForward`,也是通過`cache_getImp`查找緩存音婶,不過參數(shù)是`GETIMP`慨畸,跟上面NORMAL區(qū)別是找不到緩存時,不是走`_objc_msgSend_uncached`衣式,而是走`LGetImpMiss`寸士,最后會返回nil,避免了導(dǎo)致死循環(huán)碴卧。
imp = cache_getImp(curClass, sel);
//上面當(dāng)父類鏈的父類為nil時弱卡,imp = forward_imp進(jìn)行賦值,所以走下面這一步說明了父類鏈已經(jīng)走完都沒找到螟深。這時跳出循環(huán)谐宙,首先會調(diào)用該類的方法解析器
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;
}
//如果找到了父類imp,則跳轉(zhuǎn)done(緩存方法界弧、解鎖)凡蜻,緩存在curClass(當(dāng)次循環(huán)中curClasss是入?yún)㈩悇t緩存到入?yún)㈩悾悄臣壐割惥途彺娴侥臣壐割愔校? //1.當(dāng)動態(tài)方法決議回來第一次時'sel=resolveInstanceMethod'垢箕,這時候從cache找的imp是空的划栓,不走進(jìn)這里。
if (fastpath(imp)) {
// Found the method in a superclass. Cache it in this class.
goto done;
}
//當(dāng)前父類沒有找到条获,由于for循環(huán)沒有跳出的條件判斷忠荞,會一直死循環(huán)查找父類繼承鏈,直到break
}
// No implementation found. Try method resolver once.
//這一步代表了流程只走一次下面這段代碼(也就是只會走一次動態(tài)方法決議)帅掘,因?yàn)?behavior ^= LOOKUP_RESOLVER改變了behavior委煤,與條件behavior & LOOKUP_RESOLVER = 1不能同時滿足
//LOOKUP_RESOLVER =2,behavior & LOOKUP_RESOLVER也就是3&2=2修档,執(zhí)行條件語句代碼塊
//1.從CachLookup后進(jìn)來behavior=3碧绞,3&2為真
//2.從動態(tài)方法決議回來第一次:12,此時sel為resolveInstanceMethod吱窝,12&2為假
//3.從動態(tài)方法決議回來第二次:12讥邻,此時sel為say666,12&2為假
//也就是保證了動態(tài)方法決議只會被執(zhí)行一次
if (slowpath(behavior & LOOKUP_RESOLVER)) {
//behavior = 3^2 = 1
behavior ^= LOOKUP_RESOLVER;
//沒有找到imp院峡,則開始走本類的動態(tài)方法決議
return resolveMethod_locked(inst, sel, cls, behavior);
}
done:
//緩存方法
//當(dāng)從入?yún)㈩惢蛘吒割愭溨姓业絠mp兴使,則緩存sel和imp到當(dāng)前循環(huán)中的curClass中。1.當(dāng)從_objc_msgSend_uncached通過NORMAL進(jìn)來直接在入?yún)㈩?父類繼承鏈的方法列表中查找到imp時照激,此時sel和imp均為say666 1.如果是動態(tài)方法決議第一次進(jìn)來发魄,sel='resolveInstanceMethod',imp為resolveInstanceMethod的實(shí)現(xiàn)地址 2.如果是動態(tài)方法決議第二次進(jìn)來俩垃,此時sel='say666'欠母,imp為動態(tài)方法決議添加的say666的方法地址
log_and_fill_cache(cls, imp, sel, inst, curClass);
//解鎖
runtimeLock.unlock();
done_nolock:
//當(dāng)動態(tài)方法決議處理完成欢策,從resolveMethod_locked的lookUpImpOrForward重新進(jìn)來,此時behavior=5赏淌,5&8=0為假,執(zhí)行最后的return imp啄清。此時的imp是動態(tài)方法決議第二次進(jìn)來時緩存的say666的方法地址六水。就這樣如果動態(tài)方法決議階段+resolveInstanceMethod里正確添加了sel的imp,那在這一步就找到了imp
if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
return nil;
}
//當(dāng)動態(tài)方法決議整個流程都沒找到imp時辣卒,那么此時的imp就是forward_imp掷贾,也就是_objc_msgForward_impcache,回到MethodTableLookup荣茫,把imp的值給x17想帅,接著TailCallFunctionPointer x17,也就是調(diào)用默認(rèn)的轉(zhuǎn)發(fā)處理_objc_msgForward_impcache這個方法啡莉,進(jìn)入消息轉(zhuǎn)發(fā)流程港准。如果依然未處理,則拋出消息未識別的錯誤咧欣,終止進(jìn)程浅缸。
return imp;
}
主要步驟是:
【第一步】由于多線程可能導(dǎo)致方法緩存在另外線程添加,為了避免下面慢速查找繼承鏈方法列表需要消耗大量時間魄咕,所以再次從
cache
緩存中進(jìn)行查找衩椒,即快速查找
,找到則直接返回imp
哮兰,否則毛萌,則進(jìn)入【第二步】-
【第二步】判斷
cls
是不是已知類如果不是,則
報錯
類是否已實(shí)現(xiàn)喝滞,如果沒有阁将,則需要先實(shí)現(xiàn),確定其父類鏈或元類繼承鏈囤躁,此時實(shí)例化的目的是為了確定父類鏈冀痕、
ro
、以及rw
等狸演,方法后續(xù)數(shù)據(jù)的讀取以及查找需要是否已初始化
initialize
言蛇,如果沒有,則初始化
-
【第三步】
for循環(huán)
宵距,按照類繼承鏈 或者 元類繼承鏈
的順序查找當(dāng)前類的方法列表中使用
二分查找算法
查找方法腊尚,如果找到,則將方法寫入cache
(在iOS底層之cache_t探究中分析了寫入過程)满哪,并返回imp
婿斥,如果沒有找到劝篷,則返回nil
當(dāng)前
cls
被賦值為父類,如果父類等于nil
民宿,則imp = forward_imp
娇妓,并跳出循環(huán),進(jìn)入【第四步】如果父類鏈中存在循環(huán)活鹰,則報錯哈恰,終止循環(huán)
-
從父類緩存中查找方法
如果未找到,進(jìn)入下一次循環(huán)志群,
Method meth = getMethodNoSuper_nolock(curClass, sel);
着绷,curClass變?yōu)槠涓割悾檎腋割惙椒斜?/p>如果找到锌云,則直接返回
imp
荠医,執(zhí)行cache
寫入流程
-
【第四步】判斷是否執(zhí)行過
動態(tài)方法決議
- 如果沒有,執(zhí)行
動態(tài)方法決議
- 如果沒有,執(zhí)行
其中部分函數(shù)的源碼
- 二分法查找方法列表的imp
-
getMethodNoSuper_nolock
查找當(dāng)前類的方法列表
static method_t *
getMethodNoSuper_nolock(Class cls, SEL sel)
{
runtimeLock.assertLocked();
ASSERT(cls->isRealized());
// fixme nil cls?
// fixme nil sel?
//查找data里的methods() 方法列表
auto const methods = cls->data()->methods();
for (auto mlists = methods.beginLists(),
end = methods.endLists();
mlists != end;
++mlists)
{
// <rdar://problem/46904873> getMethodNoSuper_nolock is the hottest
// caller of search_method_list, inlining it turns
// getMethodNoSuper_nolock into a frame-less function and eliminates
// any store from this codepath.
method_t *m = search_method_list_inline(*mlists, sel);
if (m) return m;
}
return nil;
}
ALWAYS_INLINE static method_t *
search_method_list_inline(const method_list_t *mlist, SEL sel)
{
int methodListIsFixedUp = mlist->isFixedUp();
int methodListHasExpectedSize = mlist->entsize() == sizeof(method_t);
if (fastpath(methodListIsFixedUp && methodListHasExpectedSize)) {
return findMethodInSortedMethodList(sel, mlist);
} else {
// Linear search of unsorted method list
for (auto& meth : *mlist) {
if (meth.name == sel) return &meth;
}
}
#if DEBUG
// sanity-check negative results
if (mlist->isFixedUp()) {
for (auto& meth : *mlist) {
if (meth.name == sel) {
_objc_fatal("linear search worked when binary search did not");
}
}
}
#endif
return nil;
}
- 重點(diǎn)IO选彬向!上面的函數(shù),重點(diǎn)在于
findMethodInSortedMethodList(sel, mlist);
以二分法查找
ALWAYS_INLINE static method_t *
findMethodInSortedMethodList(SEL key, const method_list_t *list)
{
//list為data()中的方法列表
ASSERT(list);
const method_t * const first = &list->first;
//取base為第一個元素
const method_t *base = first;
const method_t *probe;
//key為傳進(jìn)來的cmd石洗,也就是我們調(diào)用的sayHello
uintptr_t keyValue = (uintptr_t)key;
uint32_t count;
//count賦值為列表元素個數(shù)幢泼,每一次循環(huán)都將count/2
for (count = list->count; count != 0; count >>= 1) {
//base為二分法區(qū)間的最小元素。將base平移count/2讲衫,也就是平移到列表中間的位置茴扁,probe為中間的元素
probe = base + (count >> 1);
//獲取probe存儲的名字饿肺,也就是sel方法名
uintptr_t probeValue = (uintptr_t)probe->name;
//如果查找的cmd等于sel
if (keyValue == probeValue) {
// `probe` is a match.
// Rewind looking for the *first* occurrence of this value.
// This is required for correct category overrides.
//由于分類的重寫的方法加載到內(nèi)存中是會插在類的同名方法前面,所以這里需要循環(huán)向前一個查找sel,如果有比驻,則返回分類的sel锌畸。
//如果存在多個分類重寫敷硅,則看哪個分類先加載
while (probe > first && keyValue == (uintptr_t)probe[-1].name) {
probe--;
}
return (method_t *)probe;
}
//如果cmd大于sel怎棱,則需要往后面查找,賦值最小值base為中間值probe+1拥诡,如果cmd小于sel触趴,則不走進(jìn)來
if (keyValue > probeValue) {
base = probe + 1;
count--;
}
//沒找到一致的sel,則進(jìn)入下一次循環(huán)渴肉,再取base和count的中間值
}
//如果循環(huán)到count==0冗懦,也沒找到,則返回nil
return nil;
}
其執(zhí)行流程圖如下
- 查找父類
imp
源碼:
_cache_getImp入口-> CacheLookup (GETIMP)
STATIC_ENTRY _cache_getImp
GetClassFromIsa_p16 p0
CacheLookup GETIMP, _cache_getImp
LGetImpMiss:
mov p0, #0
ret
END_ENTRY _cache_getImp
開始CacheLookup (GETIMP)仇祭,如果命中則CacheHit披蕉,未命中則CheckMiss 或者JumpMiss ->
.macro CacheLookup
//
// Restart protocol:
//
// As soon as we're past the LLookupStart$1 label we may have loaded
// an invalid cache pointer or mask.
//
// When task_restartable_ranges_synchronize() is called,
// (or when a signal hits us) before we're past LLookupEnd$1,
// then our PC will be reset to LLookupRecover$1 which forcefully
// jumps to the cache-miss codepath which have the following
// requirements:
//
// GETIMP:
// The cache-miss is just returning NULL (setting x0 to 0)
//
// NORMAL and LOOKUP:
// - x0 contains the receiver
// - x1 contains the selector
// - x16 contains the isa
// - other registers are set as per calling conventions
//
LLookupStart$1:
// p1 = SEL, p16 = isa
ldr p11, [x16, #CACHE] // p11 = mask|buckets
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
and p10, p11, #0x0000ffffffffffff // p10 = buckets
and p12, p1, p11, LSR #48 // x12 = _cmd & mask
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
and p10, p11, #~0xf // p10 = buckets
and p11, p11, #0xf // p11 = maskShift
mov p12, #0xffff
lsr p11, p12, p11 // p11 = mask = 0xffff >> p11
and p12, p1, p11 // x12 = _cmd & mask
#else
#error Unsupported cache mask storage for ARM64.
#endif
add p12, p10, p12, LSL #(1+PTRSHIFT)
// p12 = buckets + ((_cmd & mask) << (1+PTRSHIFT))
ldp p17, p9, [x12] // {imp, sel} = *bucket
1: cmp p9, p1 // if (bucket->sel != _cmd)
b.ne 2f // scan more
CacheHit $0 // call or return imp
2: // not hit: p12 = not-hit bucket
CheckMiss $0 // miss if bucket->sel == 0
cmp p12, p10 // wrap if bucket == buckets
b.eq 3f
ldp p17, p9, [x12, #-BUCKET_SIZE]! // {imp, sel} = *--bucket
b 1b // loop
3: // wrap: p12 = first bucket, w11 = mask
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
add p12, p12, p11, LSR #(48 - (1+PTRSHIFT))
// p12 = buckets + (mask << 1+PTRSHIFT)
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
add p12, p12, p11, LSL #(1+PTRSHIFT)
// p12 = buckets + (mask << 1+PTRSHIFT)
#else
#error Unsupported cache mask storage for ARM64.
#endif
// Clone scanning loop to miss instead of hang when cache is corrupt.
// The slow path may detect any corruption and halt later.
ldp p17, p9, [x12] // {imp, sel} = *bucket
1: cmp p9, p1 // if (bucket->sel != _cmd)
b.ne 2f // scan more
CacheHit $0 // call or return imp
2: // not hit: p12 = not-hit bucket
CheckMiss $0 // miss if bucket->sel == 0
cmp p12, p10 // wrap if bucket == buckets
b.eq 3f
ldp p17, p9, [x12, #-BUCKET_SIZE]! // {imp, sel} = *--bucket
b 1b // loop
LLookupEnd$1:
LLookupRecover$1:
3: // double wrap
JumpMiss $0
.endmacro
CheckMiss / JumpMiss -> LGetImpMiss,則執(zhí)行LGetImpMiss
.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
.macro JumpMiss
.if $0 == GETIMP //走這個分支
b LGetImpMiss
.elseif $0 == NORMAL
b __objc_msgSend_uncached
.elseif $0 == LOOKUP
b __objc_msgLookup_uncached
.else
.abort oops
.endif
.endmacro
其流程圖:
- 如果父類鏈沒找到
imp
,則進(jìn)入動態(tài)方法決議resolveMethod_locked
static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
runtimeLock.assertLocked();
ASSERT(cls->isRealized());
//lookUpImpOrForward沒找到imp没讲,再給你一次機(jī)會眯娱,去處理
runtimeLock.unlock();
//如果不是元類
if (! cls->isMetaClass()) {
// try [cls resolveInstanceMethod:sel]
//直接走當(dāng)前類的動態(tài)方法決議
resolveInstanceMethod(inst, sel, cls);
}
else {
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
//元類,走元類的動態(tài)方法決議
resolveClassMethod(inst, sel, cls);
//再查找一次methodList爬凑,如果還是nil徙缴,會查找一次當(dāng)前類的動態(tài)方法決議
if (!lookUpImpOrNil(inst, sel, cls)) {
//走當(dāng)前類的動態(tài)方法決議
resolveInstanceMethod(inst, sel, cls);
}
}
// chances are that calling the resolver have populated the cache
// so attempt using it
//上面給你機(jī)會去處理,現(xiàn)在重新找一次lookUpImpOrForward贰谣,此時behavior | LOOKUP_CACHE = 1|4 = 5
return lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE);
}
* resolveInstanceMethod
* Call +resolveInstanceMethod, looking for a method to be added to class cls.
* cls may be a metaclass or a non-meta class.
* Does not check if the method already exists.
**********************************************************************/
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
SEL resolve_sel = @selector(resolveInstanceMethod:);
//如果resolveInstanceMethod方法未實(shí)現(xiàn)娜搂,則直接退出,這一步會觸發(fā)一次查找lookUpImpOrNil-> lookUpImpOrForward吱抚,這時的behavior為0|LOOKUP_CACHE | LOOKUP_NIL=4|8=12
if (!lookUpImpOrNil(cls, resolve_sel, cls->ISA())) {
// Resolver not implemented.
return;
}
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(cls, resolve_sel, sel);
// Cache the result (good or bad) so the resolver doesn't fire next time.
// +resolveInstanceMethod adds to self a.k.a. cls
IMP imp = lookUpImpOrNil(inst, sel, cls);
if (resolved && PrintResolving) {
if (imp) {
_objc_inform("RESOLVE: method %c[%s %s] "
"dynamically resolved to %p",
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel), imp);
}
else {
// Method resolver didn't add anything?
_objc_inform("RESOLVE: +[%s resolveInstanceMethod:%s] returned YES"
", but no new implementation of %c[%s %s] was found",
cls->nameForLogging(), sel_getName(sel),
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel));
}
}
}
看看lookUpImpOrNil
的實(shí)現(xiàn)
static inline IMP
lookUpImpOrNil(id obj, SEL sel, Class cls, int behavior = 0)
{
return lookUpImpOrForward(obj, sel, cls, behavior | LOOKUP_CACHE | LOOKUP_NIL);
}
先調(diào)用了resolveInstanceMethod
去看看resolver
中幫沒幫實(shí)現(xiàn),如果幫實(shí)現(xiàn)了考廉,在走lookUpImpOrNil
過程中就會把方法緩存起來秘豹。然后會回到resolveMethod_locked
方法中調(diào)用lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE)
,這時可以從緩存找到imp昌粤,進(jìn)入done_nolock
, 返回方法的imp
既绕;如果整個慢速查找流程(包括動態(tài)方法決議)都沒有找到,就會返回存著forward_imp
的imp
涮坐。然后就又會回到MethodTableLookup
凄贩,把imp
的值給x17
,接著TailCallFunctionPointer x17
袱讹,調(diào)用forward_imp
也就是_objc_msgForward_impcache
這個方法疲扎,進(jìn)去消息轉(zhuǎn)發(fā)流程。
元類的動態(tài)方法決議流程和上面類實(shí)例的流程差不多捷雕,這里不再做分析椒丧。resolveClassMethod的實(shí)現(xiàn)是
* resolveClassMethod
* Call +resolveClassMethod, looking for a method to be added to class cls.
* cls should be a metaclass.
* Does not check if the method already exists.
**********************************************************************/
static void resolveClassMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
ASSERT(cls->isMetaClass());
if (!lookUpImpOrNil(inst, @selector(resolveClassMethod:), cls)) {
// Resolver not implemented.
return;
}
Class nonmeta;
{
mutex_locker_t lock(runtimeLock);
nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);
// +initialize path should have realized nonmeta already
if (!nonmeta->isRealized()) {
_objc_fatal("nonmeta class %s (%p) unexpectedly not realized",
nonmeta->nameForLogging(), nonmeta);
}
}
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(nonmeta, @selector(resolveClassMethod:), sel);
// Cache the result (good or bad) so the resolver doesn't fire next time.
// +resolveClassMethod adds to self->ISA() a.k.a. cls
IMP imp = lookUpImpOrNil(inst, sel, cls);
if (resolved && PrintResolving) {
if (imp) {
_objc_inform("RESOLVE: method %c[%s %s] "
"dynamically resolved to %p",
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel), imp);
}
else {
// Method resolver didn't add anything?
_objc_inform("RESOLVE: +[%s resolveClassMethod:%s] returned YES"
", but no new implementation of %c[%s %s] was found",
cls->nameForLogging(), sel_getName(sel),
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel));
}
}
}
為什么查找元類方法時在執(zhí)行完_class_resolveClassMethod之后會再次執(zhí)行一次_class_resolveInstanceMethod?
比如有一個類Person救巷,我們查找 Person 的一個類方法壶熏,如果沒找到,會繼續(xù)找他的第一個元類浦译,再找不到棒假,會繼續(xù)找根元類 ,最終會找到 NSObject精盅。
Person(類方法) 找——> 元類(實(shí)例方法) 找——> 根元類(實(shí)例方法) 找——> NSObject(實(shí)例方法)
實(shí)例方法存在類對象里面帽哑,類方法存在元類里面。
當(dāng)遍歷元類繼承鏈后都沒找到時渤弛,就找一遍NSObject的實(shí)例方法列表祝拯。
- 下面來看看
_objc_msgForward_impcache
做了什么__objc_msgForward_impcache
STATIC_ENTRY __objc_msgForward_impcache
// No stret specialization.
//進(jìn)入__objc_msgForward
b __objc_msgForward
END_ENTRY __objc_msgForward_impcache
ENTRY __objc_msgForward
//把存有__objc_forward_handler的頁存入X17
adrp x17, __objc_forward_handler@PAGE
//把x17向后偏移了__objc_forward_handler@PAGEOFF,然后把X17中地址的值存入p17,
ldr p17, [x17, __objc_forward_handler@PAGEOFF]
TailCallFunctionPointer x17
END_ENTRY __objc_msgForward
TailCallFunctionPointer
是個方法指針佳头,調(diào)用了上面的__objc_forward_handler
.macro TailCallFunctionPointer
// $0 = function pointer value
br $0
.endmacro
由于查找__objc_forward_handler
未找到匯編定義鹰贵,去掉一個_
查找C/C++
源碼:
// Default forward handler halts the process.
__attribute__((noreturn, cold)) void
objc_defaultForwardHandler(id self, SEL sel)
{
_objc_fatal("%c[%s %s]: unrecognized selector sent to instance %p "
"(no message forward handler is installed)",
class_isMetaClass(object_getClass(self)) ? '+' : '-',
object_getClassName(self), sel_getName(sel), self);
}
void *_objc_forward_handler = (void*)objc_defaultForwardHandler;
可以看到是由objc_defaultForwardHandler
定義的。而這個函數(shù)里面康嘉,我們看到了熟悉的方法選擇器未找到的打印信息碉输。從打印信息的組成class_isMetaClass(object_getClass(self)) ? '+' : '-'
,可以看到亭珍,系統(tǒng)內(nèi)部并沒有區(qū)分方法是實(shí)例方法還是類方法敷钾,而是通過是否是元類,來區(qū)分打印方法類型肄梨。
其流程圖:
然而在報錯之前阻荒,還會對消息進(jìn)行轉(zhuǎn)發(fā)和方法重簽名調(diào)用,再次挽救众羡。加上動態(tài)方法決議侨赡,共有3
次挽救機(jī)會。
這個流程留待下一節(jié)分析粱侣。
總結(jié)
查找實(shí)例方法
羊壹,會在類
中查找,慢速查找的繼承鏈?zhǔn)牵?code>類->父類->根類->nil
同理齐婴,查找類方法
油猫,則在元類
中查找,其慢速查找的鏈?zhǔn)牵?code>元類->根元類->根類->nil
如果objc_msgSend
快速查找緩存柠偶,慢速查找當(dāng)前類方法列表情妖、父類的緩存和方法列表都沒有找到imp
方法實(shí)現(xiàn),則嘗試動態(tài)方法決議
如果動態(tài)方法決議
仍然沒有找到嚣州,則進(jìn)行消息轉(zhuǎn)發(fā)
如果消息轉(zhuǎn)發(fā)
還沒有處理鲫售,則進(jìn)去方法簽名處理調(diào)用
。
如果方法簽名處理調(diào)用
還未處理该肴,則程序會拋出錯誤情竹。