當lookupImpOrForward函數從cache和methodTable中找不到對應Method,繼續(xù)向下執(zhí)行就會來到resolveMethod_locked函數也就是我們常說的動態(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 的實現
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個方法,我們來看一下實現。
cls->isMetaClass()的作用是判斷cls是否是元類,并且對象的實例方法是存在類中的,而類方法是存在元類中的,因此這里:
如果cls是類,也就是實例方法會調用resolveInstanceMethod方法,
如果cls是元類,類方法則會調用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;
}
//調用類的resolveInstanceMethod方法
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
//objc_msgSend(消息接收者,方法名,參數),相當于在類中調用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函數的入參依次為,實例對象、方法名、類對象。
resolveInstanceMethod函數內部會看到objc_msgSend函數,可以看到消息的接受者是cls,所以說resolveInstanceMethod是一個類方法。當系統找不到方法,系統就會調用resolveInstanceMethod
我們在LGPerson類的.m內部實現resolveInstanceMethod函數,并打印,運行:
@implementation LGPerson
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSLog(@"%s-----%@", __func__ , NSStringFromSelector(sel));
return [super resolveInstanceMethod:sel];
}
@end
從打印結果我們可以看到,系統在拋出異常之前會調用resolveInstanceMethod函數,但是會調用2次,它為什么會調用2次呢,我們在文章的最后說。

我們知道objc_msgSend本質就是通過方法名找具體實現的過程。那我們在resolveInstanceMethod函數內部去給該方法添加一個實現看看。我們首先實現一個method1方法,然后獲取該方法的imp,添加到本類里面。

這個樣運行時就不會拋出異常了,并且正常調用了method1。
當我們動態(tài)的添加了resolveInstanceMethod()方法的時候,系統還是會調用lookUpImpOrNilTryCache()方法,去找IMP。

這個時候這個imp會不會被緩存呢,就是method1這個方法會不會被緩存,我們驗證一下。
通過獲取cache里的內容我們找到了test方法,也就是說當方法進行動態(tài)決議的時候是會被緩存的.
下面我們來看一下類方法沒有實現時怎么處理。
剛才我們看到有一個resolveClassMethod, 我們去調用一下類方法test2


打印確實看到調用了resolveClassMethod,并走到自己添加的實現。

再次回到resolveMethod_locked方法中,我們看到元類在調用了resolveClassMethod之后,如果元類中沒有imp,那么又再一次調用了resolveInstanceMethod,這是為什么呢?

我們知道實例方法是存在類里邊,而類方法是存放在元類中;那如果類方法找不到的時候,我們應該調用元類的 resolveInstanceMethod。但是元類我們無法修改。根據類與元類的繼承關系,就會繼續(xù)往根元類找,最終找到NSObject的resolveInstanceMethod方法。這一整套調用鏈路會變得非常長,影響系統運行效率;(如果NSObject沒有resolveInstanceMethod方法,我們可以通過寫分類進行添加)。因此蘋果提供resolveClassMethod方法,其實就是為了簡化類方法的查找流程,方便在類方法找不到時,直接通過resolveClassMethod來進行類方法決議,提升調用效率;

當類方法在動態(tài)決議時,如果沒有找到實現他還會調用resolveInstanceMethod()方法,所以還會調用method1()方法
下面這個例子,在元類中找方法的實現找不到,就要動態(tài)決議,會一直找, 變成死循環(huán)

在NSObject分類中寫上這個方法,不管是實例方法還是類方法找不到的崩潰就不會出現了,有利于程序的穩(wěn)定,這么寫類似aop(面向切面對象:在不修改源代碼的情況下,通過運行時來給程序添加統一的功能,用來進行埋點)

消息轉發(fā)
如果系統在動態(tài)決議階段沒有找到實現,就會進入消息轉發(fā)階段。如果類中沒有實現resolveInstanceMethod方法,就會調用methodSignatureForSelector方法,我們就看下消息轉發(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判斷中有對打印控制的參數,說明可以通過設置這個參數來進行打印日志;全局查找,我們可以找到設置objcMsgLogEnabled的函數,也就是通過instrumentObjcMessageSends函數來設置是否打印日志。
/tmp/msgSends-%d通過查看logMessageSend的源碼如下圖可以看到,日志輸出的路徑為/tmp/msgSends-XXX,我們可以在此路徑下找到這個日志打印文件。


我們調用instrumentObjcMessageSends函數前,需要先聲明下該函數,如下:
extern void instrumentObjcMessageSends(BOOL flag);
然后打開該功能
instrumentObjcMessageSends(YES);
運行后,就可以在/tmp目錄下找到該文件:

可以看到在崩潰之前還調用了兩個方法forwardingTargetForSelector和methodSignatureForSelector方法。消息發(fā)送在經過動態(tài)方法解析仍然沒有查找到真正的方法實現,此時動態(tài)方法決議進入imp = forward_imp消息轉發(fā)流程。轉發(fā)流程分兩步快速轉發(fā)和慢速轉發(fā)。
消息的快速轉發(fā)
我們在LGPerson類中聲明test3方法,不實現,然后定義一個LGBoy類, 在LGBoy類中實現test1,然后在LGPerson類中實現forwardingTargetForSelector:方法,將LGPerson的test3方法轉發(fā)到LGBoy類中,也就是說,快速轉發(fā)后的類必須有同名的方法。如下代碼:


轉發(fā)的作用在于,如果當前對象無法響應消息,就將它轉發(fā)給能響應的對象。并且方法緩存在接收轉發(fā)消息的對象的cache中
消息的慢速轉發(fā)
在快速轉發(fā)過程中,如果我們不做處理,此時就會進入到methodSignatureForSelector方法, 也就是慢速轉發(fā)。

消息轉發(fā)流程總結
