經過上一篇章的學習,OC調用方法,底層是調用 objc_msgSend 發送消息。在發送消息時會通過一系列的快速 查找、慢速查找,若是查找到對應的 IMP,直接返回;若是沒有找到,就會進入到方法的動態解析和消息轉發流程。 緩存
經過探索objc_msgSend
源碼,當慢速查找依然沒有找到IMP
時,會進入方法動態解析階段,源碼以下:bash
_class_resolveMethod
方法後,在進行一次
retry
,從新進行一遍方法的查找流程,而只有一次動態方法解析的機會就是在
_class_resolveMethod
方法中。
_class_resolveMethod
源碼以下:學習
void _class_resolveMethod(Class cls, SEL sel, id inst)
{
// 是不是元類
if (! cls->isMetaClass()) {
// try [cls resolveInstanceMethod:sel]
_class_resolveInstanceMethod(cls, sel, inst);
}
else {
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
_class_resolveClassMethod(cls, sel, inst); // 已經處理
if (!lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
// 對象方法 決議
_class_resolveInstanceMethod(cls, sel, inst);
}
}
}
複製代碼
由於類方法是存儲在元類中,因此在_class_resolveMethod
中的處理有所不一樣ui
元 類:說明是對元類中的類方法進行處理,可是元類中的方法是在根元類中以實例方法的形式存儲的,因此
最終會查找根元類的實例方法,調用實例方法解析查找。
非元類:對儲存在類中的實例方法進行處理。
複製代碼
在_class_resolveInstanceMethod
方法中對實例方法動態解析,源碼以下:spa
static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst)
{
// 1. 判斷系統是否實現SEL_resolveInstanceMethod方法
// 即+(BOOL)resolveInstanceMethod:(SEL)sel,
// 繼承自NSObject的類,默認實現,返回NO
if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
// Resolver not implemented.
// 不是NSObject的子類,也未實現+(BOOL)resolveInstanceMethod:(SEL)sel,
// 直接返回,沒有動態解析的必要
return;
}
// 2. 系統給你一次機會 - 你要不要針對 sel 來操做一下下
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(cls, SEL_resolveInstanceMethod, sel);
// 3. 再次尋找IMP
IMP imp = lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/);
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)); } } } 複製代碼
由此:咱們能夠在+(BOOL)resolveInstanceMethod:(SEL)sel
方法中對未實現的方法指定已實現方法的IMP,並添加到類中,實現方法動態解析,防止系統崩潰。3d
+ (BOOL)resolveInstanceMethod:(SEL)sel{
NSLog(@"來了老弟:%s - %@",__func__,NSStringFromSelector(sel));
if (sel == @selector(saySomething)) {
NSLog(@"說話了");
IMP sayHIMP = class_getMethodImplementation(self, @selector(sayHello));
Method sayHMethod = class_getInstanceMethod(self, @selector(sayHello));
const char *sayHType = method_getTypeEncoding(sayHMethod);
return class_addMethod(self, sel, sayHIMP, sayHType);
}
return [super resolveInstanceMethod:sel];
}
複製代碼
咱們也能夠在此方法中根據方法的前綴、路由、事務,跳轉的不一樣的頁面,進行bug收集。日誌
若是是元類,在_class_resolveClassMethod
方法中對相關類方法的進行動態解析,該方法的實現步驟和實例方法的實現步驟相似,區別是消息發送的時候獲取的是元類,即:code
_class_resolveClassMethod
源碼以下:orm
static void _class_resolveClassMethod(Class cls, SEL sel, id inst)
{
assert(cls->isMetaClass());
// 1. 判斷系統是否實現SEL_resolveClassMethod方法
// 即+(BOOL)resolveClassMethod:(SEL)sel,
// 繼承自NSObject的類,默認實現,返回NO
if (! lookUpImpOrNil(cls, SEL_resolveClassMethod, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
// Resolver not implemented.
return;
}
//2. 系統給你一次機會
// 經過objc_msgSend調用一下+(BOOL)resolveClassMethod:(SEL)sel方法
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(_class_getNonMetaClass(cls, inst),
SEL_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 //3. 再次查找IMP IMP imp = lookUpImpOrNil(cls, sel, inst, NO/*initialize*/, YES/*cache*/, NO/*resolver*/); 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));
}
}
}
複製代碼
所以,當咱們要進行類方法的動態解析時,須要添加+ (BOOL)resolveClassMethod:(SEL)sel進行動態方法解析:cdn
+ (BOOL)resolveClassMethod:(SEL)sel{
NSLog(@"來了類方法:%s - %@",__func__,NSStringFromSelector(sel));
if (sel == @selector(sayLove)) {
NSLog(@"說- 說你你愛我");
IMP sayHIMP = class_getMethodImplementation(self, @selector(sayObjc));
Method sayHMethod = class_getClassMethod(self, @selector(sayObjc));
const char *sayHType = method_getTypeEncoding(sayHMethod);
// 類方法在元類 objc_getMetaClass("LGStudent")
return class_addMethod(self, sel, sayHIMP, sayHType);
}
return [super resolveClassMethod:sel];
}
複製代碼
resolveInstanceMethod
方法resolveClassMethod
方法元類
繼承自根元類
,根元類
最終繼承自NSObject
,所以對類方法解析的時候,最終會查找到NSObject
。因爲元類和根源類由系統建立,沒法修改,因此能夠再根元類的父類NSObject
中,添加對應的實例方法resolveInstanceMethod
進行動態解析。在方法查找過程當中,通過緩存查找,方法列表查找和動態方法解析,若是以上步驟都沒有查找到IMP
,也沒有進行方法動態解析,那麼就會進入最後一步,崩潰。
_objc_msgForward_impcache
是彙編方法,以下:
STATIC_ENTRY __objc_msgForward_impcache
// Method cache version
// THIS IS NOT A CALLABLE C FUNCTION
// Out-of-band Z is 0 (EQ) for normal, 1 (NE) for stret
beq __objc_msgForward
b __objc_msgForward_stret
END_ENTRY __objc_msgForward_impcache
ENTRY __objc_msgForward
// Non-stret version
MI_GET_EXTERN(r12, __objc_forward_handler)
ldr r12, [r12]
bx r12
END_ENTRY __objc_msgForward
ENTRY __objc_msgForward_stret
複製代碼
在 _objc_msgForward_impcache
中,調用__objc_msgForward
,而後調用__objc_forward_handler
,轉掉_objc_forward_handler OC
方法以下,而後就是經典崩潰。
經過查看那麼,在崩潰時,爲何會打印如上圖的一系列堆棧信息呢 ?
lookUpImpOrForward
源碼,如上圖,當查找到
IMP
時,會調用
log_and_fill_cache
方法,進行緩存填充和日誌存儲。
log_and_fill_cache
如上圖,經過控制
objcMsgLogEnabled
來控制日誌存儲,日誌會記錄在
/tmp/msgSends
目錄下,而
objcMsgLogEnabled
的賦值是在
instrumentObjcMessageSends
之中,能夠暴露這個方法,來達到外部打日誌的操做。
在查看/tmp/msgSends
目錄下的文件,如圖:
resolveInstanceMethod:
,
forwardingTargetForSelector
,
methodSignatureForSelector
,
doesNotRecognizeSelector
一系列方法,進行消息轉發。
經過查看forwardingTargetForSelector
的官方文檔,
- (id)forwardingTargetForSelector:(SEL)aSelector
方法,
1. 返回一個對象。若是這個對象非空、非nil,系統會將消息轉發給這個對象執行,不然,繼續查找其餘流程。
系統給了將這個SEL轉給其餘對象的機會。
2. 若是返回nil,或者沒有處理消息轉發,會走到forwardInvocation:方法進行處理,進入慢速消息轉發流程。
複製代碼
能夠經過一下代碼,將saySomething方法
的消息轉發到LGTeacher
類中實現,而不會引發系統崩潰,至此消息快速轉發結束。
- (id)forwardingTargetForSelector:(SEL)aSelector{
NSLog(@"%s -- %@",__func__,NSStringFromSelector(aSelector));
if (aSelector == @selector(saySomething)) {
return [LGTeacher alloc];
}
return [super forwardingTargetForSelector:aSelector];
}
複製代碼
進入慢速查找流程,首先必須先實現methodSignatureForSelector
方法,返回一個簽名,這個方法簽名裏面封裝了返回值類型,參數類型等信息。
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
NSLog(@"%s -- %@",__func__,NSStringFromSelector(aSelector));
if (aSelector == @selector(saySomething)) { // v @ :
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
複製代碼
而後還必須實現- (void)forwardInvocation:(NSInvocation *)anInvocation
;
- (void)forwardInvocation:(NSInvocation *)invocation
{
SEL aSelector = [invocation selector];
if ([friend respondsToSelector:aSelector])
[invocation invokeWithTarget:friend];
else
[super forwardInvocation:invocation];
}
複製代碼
注意:
1. forwardInvocation 方法和 methodSignatureForSelector 方法必須同時實現
2. methodSignatureForSelector 會生成一個簽名,NSInvocation對象,將NSInvocation對象做爲
參數傳給 forwardInvocation 方法的
3. 在forwardInvocation方法裏面將消息給能處理該消息的對象,以免對象調用
didNotRecognizeSelector 方法致使崩潰
4. forwardInvocation 這個方法相似於將消息當作事務堆放起來,在這裏誰能夠操做就在這裏面操做,
就算不操做也不會崩潰,這裏也是防崩潰的最後處理機會。
複製代碼
接下來看一下系統NSObject
中forwardInvocation
的實現:
+ (void)forwardInvocation:(NSInvocation *)invocation {
[self doesNotRecognizeSelector:(invocation ? [invocation selector] : 0)];
}
- (void)forwardInvocation:(NSInvocation *)invocation {
[self doesNotRecognizeSelector:(invocation ? [invocation selector] : 0)];
}
// Replaced by CF (throws an NSException)
+ (void)doesNotRecognizeSelector:(SEL)sel {
_objc_fatal("+[%s %s]: unrecognized selector sent to instance %p",
class_getName(self), sel_getName(sel), self);
}
// Replaced by CF (throws an NSException)
- (void)doesNotRecognizeSelector:(SEL)sel {
_objc_fatal("-[%s %s]: unrecognized selector sent to instance %p",
object_getClassName(self), sel_getName(sel), self);
}
複製代碼
因而可知,系統最後是在doesNotRecognizeSelector
方法中拋出異常的,因此重寫forwardInvocation
方法後,無論裏面有麼有實現,或者執行父類的方法,程序都是不會崩潰的。
消息轉發流程圖:
當調用了未實現的方法,三個解決途徑:
一、resolveInstanceMethod:爲發送消息的對象的添加一個IMP,而後再讓該對象去處理
二、forwardingTargetForSelector:將該消息轉發給能處理該消息的對象
三、methodSignatureForSelector和 forwardInvocation:第一個方法生成方法簽名,而後建立
NSInvocation 對象做爲參數給第二個方法,而後在forwardInvocation 方法裏面作消息處理,
只要在第二個方法裏面不執行父類的方法,即便不處理也不會崩潰
複製代碼