通過上一章的學習,咱們瞭解到了方法是如何經歷快速和慢速的流程進行查找的,若是通過方法的查找,能夠找到對應IMP的話,則會直接返回,若是沒有找到,就要進入本章的動態方法解析和消息轉發流程了緩存
經過上一章,咱們基本瞭解了方法查找的3個階段以下:bash
消息發送階段:從類及父類的方法緩存列表及方法列表查找方法;post
動態解析階段:若是消息發送階段沒有找到方法,則會進入動態解析階段,負責動態的添加方法實現;ui
消息轉發階段:若是也沒有實現動態解析方法,則會進行消息轉發階段,將消息轉發給能夠處理消息的接受者來處理;編碼
經過閱讀lookUpImpOrForward
源碼,咱們知道動態方法解析,主要在_class_resolveMethod
中,消息轉發,主要在_objc_msgForward_impcache
中,自此,咱們來逐個進行研究spa
在lookUpImpOrForward
方法中,通過對類,父類,元類的緩存和方法列表的查詢後,仍舊沒有找到方法,則會進入動態方法解析階段,源碼以下,咱們能夠看到,在通過 _class_resolveMethod
後,會進行一遍retry
操做,從新進行一遍方法的查找流程,而且只有一次動態方法解析的機會 3d
_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_resolveInstanceMethod
該方法是進行實例方法動態解析的主要實現方法,咱們經過源碼來逐行分析日誌
static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst)
{
️✅// 判斷系統是否實現SEL_resolveInstanceMethod方法,即+(BOOL)resolveInstanceMethod:(SEL)sel
if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
️✅// 若是沒有找到(通常是不繼承子NSObject的類),則直接返回
return;
}
️✅// 若是找到,則經過objc_msgSend調用一下+(BOOL)resolveInstanceMethod:(SEL)sel方法
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(cls, SEL_resolveInstanceMethod, sel);
// Cache the result (good or bad) so the resolver does not fire next time.
// +resolveInstanceMethod adds to self a.k.a. cls
️✅// 同時再次尋找方法的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)); } } } 複製代碼
經過閱讀源碼,咱們知道,若是要進行方法的動態解析的話,則須要再系統方法code
+ (BOOL)resolveInstanceMethod:(SEL)sel
中進行處理,咱們須要經過對未實現的方法指定一個已經實現的方法的IMP,並添加到類中,實現方法動態解析,這樣咱們就能夠對一個未實現的方法進行動態解析了
+ (BOOL)resolveInstanceMethod:(SEL)sel{
️✅// 獲取到須要動態解析的方法名
if (sel == @selector(saySomething)) {
NSLog(@"說話了");
️✅// 獲取到須要動態解析到的方法sayHello的IMP和Method
IMP sayHIMP = class_getMethodImplementation(self, @selector(sayHello));
Method sayHMethod = class_getInstanceMethod(self, @selector(sayHello));
const char *sayHType = method_getTypeEncoding(sayHMethod);
️✅// 經過API添加方法
return class_addMethod(self, sel, sayHIMP, sayHType);
}
return [super resolveInstanceMethod:sel];
}
複製代碼
_class_resolveClassMethod
若是是元類,則相關類方法的處理在_class_resolveClassMethod
方法中處理,該方法的實現步驟和實例方法的實現步驟相似,只不過是消息發送的時候獲取的是元類
+ (BOOL)resolveClassMethod:(SEL)sel
進行動態方法解析
+ (BOOL)resolveClassMethod:(SEL)sel{
if (sel == @selector(sayLove)) {
️✅// 獲取到元類中存儲的類方法sayObjc
IMP sayHIMP = class_getMethodImplementation(objc_getMetaClass("LGStudent"), @selector(sayObjc));
Method sayHMethod = class_getClassMethod(objc_getMetaClass("LGStudent"), @selector(sayObjc));
const char *sayHType = method_getTypeEncoding(sayHMethod);
️✅// 將類方法實現添加在元類之中
return class_addMethod(objc_getMetaClass("LGStudent"), sel, sayHIMP, sayHType);
}
return [super resolveClassMethod:sel];
}
複製代碼
resolveInstanceMethod
方法,並添加方法在類中resolveClassMethod
方法,並添加方法在元類中resolveInstanceMethod
進行動態解析在lookUpImpOrForward
方法中,通過對接受對象緩存,方法列表查找和動態方法解析後,若是以上步驟都沒有進行處理,那麼就會進入,消息處理的最後一步,即消息轉發流程,這是補救方法崩潰最終的一步了,若是不想方法崩潰,那此時必定要處理了。
imp = (IMP)_objc_msgForward_impcache;
cache_fill(cls, sel, imp, inst);
複製代碼
_objc_msgForward_impcache
也是一段彙編的代碼,經過代碼咱們能夠知道,彙編通過了一段__objc_msgForward
方法,發現裏面只有一段崩潰的實現,可是根據崩潰信息,咱們能夠發現中間還通過了___forwarding___
和_CF_forwarding_prep_0
等方法,可是是在CoreFoundation
庫中的,因此消息轉發的處理在此時進行的
log_and_fill_cache
,說明系統會對緩存的方法加日誌,咱們能夠經過系統的日誌在查看方法調用的狀況。經過方法咱們能夠看到,日誌會記錄在
/tmp/msgSends
目錄下,且經過
objcMsgLogEnabled
變量來控制是否存儲日誌
objcMsgLogEnabled
的賦值是在
instrumentObjcMessageSends
之中,因此咱們能夠暴露這個方法,來達到外部打日誌的操做
instrumentObjcMessageSends
方法,並定位在要崩潰的方法中,能夠打上日誌,來查看調用
extern void instrumentObjcMessageSends(BOOL flag);
int main(int argc, const char * argv[]) {
@autoreleasepool {
LGStudent *student = [LGStudent alloc] ;
instrumentObjcMessageSends(true);
[student saySomething];
instrumentObjcMessageSends(false);
}
return 0;
}
複製代碼
經過尋找/tmp/msgSends
文件,以下圖所示,咱們發現通過了resolveInstanceMethod
,forwardingTargetForSelector
,methodSignatureForSelector
,doesNotRecognizeSelector
,這就是咱們要尋找的處理方法。
其中resolveInstanceMethod
爲方法動態解析,doesNotRecognizeSelector
爲通過上述的最後崩潰調用
因此,最終消息的轉發就再forwardingTargetForSelector
和methodSignatureForSelector
中了,這也是一個快速
和慢速
兩種流程
forwardingTargetForSelector
經過查看- (id)forwardingTargetForSelector:(SEL)aSelector
方法的文檔,咱們可得
forwardInvocation:
方法進行處理objc_msgSend(forwardingTarget, sel, ...);
來實現消息的發送saySomething
方法的處理被轉發到
LGTeacher
的相關類中實現。至此消息轉發的快速流程結束,不會崩潰
- (id)forwardingTargetForSelector:(SEL)aSelector{
NSLog(@"%s -- %@",__func__,NSStringFromSelector(aSelector));
if (aSelector == @selector(saySomething)) {
return [LGTeacher alloc];
}
return [super forwardingTargetForSelector:aSelector];
}
複製代碼
methodSignatureForSelector
若是沒有通過上述的消息轉發的快速流程,那麼會進入一個消息轉發的慢速流程之中,實現慢速流程。
首先必須實現methodSignatureForSelector
方法,經過下面文檔的能夠得出
而後實現methodSignatureForSelector
後,還必須實現- (void)forwardInvocation:(NSInvocation *)anInvocation;
方法進行處理,經過文檔,咱們可得
forwardInvocation
和methodSignatureForSelector
必須是同時存在的,底層會經過方法簽名,生成一個NSInvocation
,將其做爲參數傳遞調用InInvocation
中編碼的消息的對象。對於全部消息,此對象沒必要相同。anInvocation
將消息發送到該對象。anInvocation
將保存結果,運行時系統將提取結果並將其傳遞給原始發送者。咱們能夠看到NSInvocation
源碼
- (void)getArgument:(void *)argumentLocation atIndex:(NSInteger)idx;
-- 方法參數,所以在此方法裏面,咱們能夠決定將消息轉發給誰(target),甚至還能夠修改消息的參數,因爲anInvocation
會存儲消息selector
裏面帶來的參數,而且能夠根據消息所對應的方法簽名肯定消息參數的個數,因此咱們經過- (void)setArgument:(void *)argumentLocation atIndex:(NSInteger)idx;能夠對參數進行修改。總之你能夠按照你的意願,配置好anInvocation,而後執行[anInvocation invoke];便可完成消息的轉發調用
- (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{
NSLog(@"%s ",__func__);
SEL aSelector = [anInvocation selector];
if ([[LGTeacher alloc] respondsToSelector:aSelector])
[anInvocation invokeWithTarget:[LGTeacher alloc]];
else
[super forwardInvocation:anInvocation];
}
複製代碼
forwardingTargetForSelector
實現消息轉發的快速流程,直接轉發到能處理相關方法的對象中,而且方法要保持一致methodSignatureForSelector
提供一個方法簽名,用來生成NSInvocation
參數進行後續的使用forwardInvocation
經過對NSInvocation
來實現消息的最終轉發NSObject
中doesNotRecognizeSelector
方法,致使方法尋找不到,程序崩潰
MJ大神提供的相關消息轉發C語言實現源碼
int __forwarding__(void *frameStackPointer, int isStret) {
id receiver = *(id *)frameStackPointer;
SEL sel = *(SEL *)(frameStackPointer + 8);
const char *selName = sel_getName(sel);
Class receiverClass = object_getClass(receiver);
// 調用 forwardingTargetForSelector:
if (class_respondsToSelector(receiverClass, @selector(forwardingTargetForSelector:))) {
id forwardingTarget = [receiver forwardingTargetForSelector:sel];
if (forwardingTarget && forwardingTarget != receiver) {
return objc_msgSend(forwardingTarget, sel, ...);
}
}
// 調用 methodSignatureForSelector 獲取方法簽名後再調用 forwardInvocation
if (class_respondsToSelector(receiverClass, @selector(methodSignatureForSelector:))) {
NSMethodSignature *methodSignature = [receiver methodSignatureForSelector:sel];
if (methodSignature && class_respondsToSelector(receiverClass, @selector(forwardInvocation:))) {
NSInvocation *invocation = [NSInvocation _invocationWithMethodSignature:methodSignature frame:frameStackPointer];
[receiver forwardInvocation:invocation];
void *returnValue = NULL;
[invocation getReturnValue:&value];
return returnValue;
}
}
if (class_respondsToSelector(receiverClass,@selector(doesNotRecognizeSelector:))) {
[receiver doesNotRecognizeSelector:sel];
}
// The point of no return.
kill(getpid(), 9);
}
複製代碼