當(dāng)lookupImpOrForward函數(shù)從cache和methodTable中找不到對應(yīng)Method,繼續(xù)向下執(zhí)行就會來到resolveMethod_locked函數(shù)也就是我們常說的動態(tài)方法決議
// No implementation found. Try method resolver once.
if (slowpath(behavior & LOOKUP_RESOLVER)) {
behavior ^= LOOKUP_RESOLVER;
return resolveMethod_locked(inst, sel, cls, behavior);
}
接下來看一下 resolveMethod_locked 的實現(xiàn)
static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
runtimeLock.assertLocked();
ASSERT(cls->isRealized());
runtimeLock.unlock();
if (! cls->isMetaClass()) {
// try [cls resolveInstanceMethod:sel]
resolveInstanceMethod(inst, sel, cls);
}
else {
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
resolveClassMethod(inst, sel, cls);
if (!lookUpImpOrNilTryCache(inst, sel, cls)) {
resolveInstanceMethod(inst, sel, cls);
}
}
// chances are that calling the resolver have populated the cache
// so attempt using it
return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);
}
里面有 resolveInstanceMethod 和 resolveClassMethod 2個方法稿存,我們來看一下實現(xiàn)晶姊。
cls->isMetaClass()的作用是判斷cls是否是元類朽基,并且對象的實例方法是存在類中的黑忱,而類方法是存在元類中的全庸,因此這里:
如果cls是類使兔,也就是實例方法會調(diào)用resolveInstanceMethod方法靶端,
如果cls是元類谎势,類方法則會調(diào)用resolveClassMethod方法,
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
//首先定義resolveInstanceMethod的方法
SEL resolve_sel = @selector(resolveInstanceMethod:);
//先嘗試在類的緩存中查找是否有該resolveInstanceMethod方法
if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) {
// Resolver not implemented.
//沒有返回
return;
}
//調(diào)用類的resolveInstanceMethod方法
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
//objc_msgSend(消息接收者杨名,方法名脏榆,參數(shù)),相當(dāng)于在類中調(diào)用resolveInstanceMethod方法台谍,返回true代表處理了該方法姐霍,否則就有問題。
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 = lookUpImpOrNilTryCache(inst, sel, cls);
if (resolved && PrintResolving) {
//處理錯誤信息
}
}
resolveInstanceMethod函數(shù)的入?yún)⒁来螢椋瑢嵗龑ο竽髡邸⒎椒韪㈩悓ο蟆?br> resolveInstanceMethod函數(shù)內(nèi)部會看到objc_msgSend函數(shù),可以看到消息的接受者是cls恨胚,所以說resolveInstanceMethod是一個類方法骂因。當(dāng)系統(tǒng)找不到方法,系統(tǒng)就會調(diào)用resolveInstanceMethod
我們在LGPerson類的.m內(nèi)部實現(xiàn)resolveInstanceMethod函數(shù)赃泡,并打印寒波,運行:
@implementation LGPerson
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSLog(@"%s-----%@", __func__ , NSStringFromSelector(sel));
return [super resolveInstanceMethod:sel];
}
@end
從打印結(jié)果我們可以看到,系統(tǒng)在拋出異常之前會調(diào)用resolveInstanceMethod函數(shù)升熊,但是會調(diào)用2次俄烁,它為什么會調(diào)用2次呢,我們在文章的最后說级野。
我們知道objc_msgSend本質(zhì)就是通過方法名找具體實現(xiàn)的過程页屠。那我們在resolveInstanceMethod函數(shù)內(nèi)部去給該方法添加一個實現(xiàn)看看。我們首先實現(xiàn)一個method1方法蓖柔,然后獲取該方法的imp辰企,添加到本類里面。
這個樣運行時就不會拋出異常了况鸣,并且正常調(diào)用了method1牢贸。
當(dāng)我們動態(tài)的添加了resolveInstanceMethod()方法的時候,系統(tǒng)還是會調(diào)用lookUpImpOrNilTryCache()方法镐捧,去找IMP潜索。
這個時候這個imp會不會被緩存呢,就是method1這個方法會不會被緩存懂酱,我們驗證一下竹习。
通過獲取cache里的內(nèi)容我們找到了test方法,也就是說當(dāng)方法進行動態(tài)決議的時候是會被緩存的.
下面我們來看一下類方法沒有實現(xiàn)時怎么處理玩焰。
剛才我們看到有一個resolveClassMethod由驹, 我們?nèi)フ{(diào)用一下類方法test2
打印確實看到調(diào)用了resolveClassMethod芍锚,并走到自己添加的實現(xiàn)昔园。
再次回到resolveMethod_locked方法中,我們看到元類在調(diào)用了resolveClassMethod之后并炮,如果元類中沒有imp默刚,那么又再一次調(diào)用了resolveInstanceMethod,這是為什么呢逃魄?
我們知道實例方法是存在類里邊荤西,而類方法是存放在元類中;那如果類方法找不到的時候,我們應(yīng)該調(diào)用元類的 resolveInstanceMethod邪锌。但是元類我們無法修改勉躺。根據(jù)類與元類的繼承關(guān)系,就會繼續(xù)往根元類找觅丰,最終找到NSObject的resolveInstanceMethod方法饵溅。這一整套調(diào)用鏈路會變得非常長,影響系統(tǒng)運行效率妇萄;(如果NSObject沒有resolveInstanceMethod方法蜕企,我們可以通過寫分類進行添加)。因此蘋果提供resolveClassMethod方法冠句,其實就是為了簡化類方法的查找流程轻掩,方便在類方法找不到時,直接通過resolveClassMethod來進行類方法決議懦底,提升調(diào)用效率唇牧;
當(dāng)類方法在動態(tài)決議時,如果沒有找到實現(xiàn)他還會調(diào)用resolveInstanceMethod()方法基茵,所以還會調(diào)用method1()方法
下面這個例子奋构,在元類中找方法的實現(xiàn)找不到,就要動態(tài)決議拱层,會一直找, 變成死循環(huán)
在NSObject分類中寫上這個方法弥臼,不管是實例方法還是類方法找不到的崩潰就不會出現(xiàn)了,有利于程序的穩(wěn)定根灯,這么寫類似aop(面向切面對象:在不修改源代碼的情況下径缅,通過運行時來給程序添加統(tǒng)一的功能,用來進行埋點)
消息轉(zhuǎn)發(fā)
如果系統(tǒng)在動態(tài)決議階段沒有找到實現(xiàn)烙肺,就會進入消息轉(zhuǎn)發(fā)階段纳猪。如果類中沒有實現(xiàn)resolveInstanceMethod方法,就會調(diào)用methodSignatureForSelector方法桃笙,我們就看下消息轉(zhuǎn)發(fā)流程氏堤。
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
省略代碼...
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);
}
省略代碼...
}
-------
static void
log_and_fill_cache(Class cls, IMP imp, SEL sel, id receiver, Class implementer)
{
#if SUPPORT_MESSAGE_LOGGING
if (slowpath(objcMsgLogEnabled && implementer)) {
bool cacheIt = logMessageSend(implementer->isMetaClass(),
cls->nameForLogging(),
implementer->nameForLogging(),
sel);
if (!cacheIt) return;
}
#endif
cls->cache.insert(sel, imp, receiver);
}
log_and_fill_cache方法中最主要的就是進行方法緩存cache.insert,但這里還有一個logMessageSend進行消息信息打印的過程搏明,那么我們該如何獲取這個打印的消息呢鼠锈?
objcMsgLogEnabled我們看到if判斷中有對打印控制的參數(shù),說明可以通過設(shè)置這個參數(shù)來進行打印日志星著;全局查找购笆,我們可以找到設(shè)置objcMsgLogEnabled的函數(shù),也就是通過instrumentObjcMessageSends函數(shù)來設(shè)置是否打印日志虚循。
/tmp/msgSends-%d通過查看logMessageSend的源碼如下圖可以看到同欠,日志輸出的路徑為/tmp/msgSends-XXX样傍,我們可以在此路徑下找到這個日志打印文件。
我們調(diào)用instrumentObjcMessageSends函數(shù)前铺遂,需要先聲明下該函數(shù)衫哥,如下:
extern void instrumentObjcMessageSends(BOOL flag);
然后打開該功能
instrumentObjcMessageSends(YES);
運行后,就可以在/tmp目錄下找到該文件:
可以看到在崩潰之前還調(diào)用了兩個方法forwardingTargetForSelector和methodSignatureForSelector方法襟锐。消息發(fā)送在經(jīng)過動態(tài)方法解析仍然沒有查找到真正的方法實現(xiàn)炕檩,此時動態(tài)方法決議進入imp = forward_imp消息轉(zhuǎn)發(fā)流程。轉(zhuǎn)發(fā)流程分兩步快速轉(zhuǎn)發(fā)和慢速轉(zhuǎn)發(fā)捌斧。
消息的快速轉(zhuǎn)發(fā)
我們在LGPerson類中聲明test3方法笛质,不實現(xiàn),然后定義一個LGBoy類, 在LGBoy類中實現(xiàn)test1捞蚂,然后在LGPerson類中實現(xiàn)forwardingTargetForSelector:方法妇押,將LGPerson的test3方法轉(zhuǎn)發(fā)到LGBoy類中,也就是說姓迅,快速轉(zhuǎn)發(fā)后的類必須有同名的方法敲霍。如下代碼:
轉(zhuǎn)發(fā)的作用在于,如果當(dāng)前對象無法響應(yīng)消息丁存,就將它轉(zhuǎn)發(fā)給能響應(yīng)的對象肩杈。并且方法緩存在接收轉(zhuǎn)發(fā)消息的對象的cache中
消息的慢速轉(zhuǎn)發(fā)
在快速轉(zhuǎn)發(fā)過程中,如果我們不做處理解寝,此時就會進入到methodSignatureForSelector方法扩然, 也就是慢速轉(zhuǎn)發(fā)。