IOS objc_msgSend執行流程
objc_msgSend大家應該不陌生吧,oc的方法呼叫,其實就是轉換為objc_msgSend的函式呼叫。簡答的可以理解為發訊息,如果 方法呼叫 之後出現了經典的錯誤,unrecognized selector sent to instance... 也可以從以下三個階段進行分析。
objc_msgSend執行流程可以分為三個階段
- 訊息傳送
XZdog *dog = [[XZdog alloc]init];
[dog test];
//通過生成的 .cpp檔案可以看到底層程式碼的實現
// objc_msgSend(dog, sel_registerName("test"));
- 動態方法解析
@interface XZdog : NSObject
- (void)test;
@end
//方法一
#import "XZdog.h"
#import <objc/runtime.h>
@implementation XZdog
- (void)other{
NSLog(@"---%s",__func__);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(test)) {
struct method_t *meth=(struct method_t*) class_getInstanceMethod(self, @selector(other));
NSLog(@"%s %s %p",meth->sel,meth->types,meth->imp);
class_addMethod(self, sel, meth->imp, meth->types);
//表示有動態新增方法,這是規範,可以參考官方文件
return YES;
}
return [super resolveInstanceMethod:sel];
}
@end
//方法二
@implementation XZdog
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(test)) {
Method meth= class_getInstanceMethod(self, @selector(other));
class_addMethod(self, sel, method_getImplementation(meth), method_getTypeEncoding(meth));
//表示有動態新增方法,這是規範,可以參考官方文件
return YES;
}
return [super resolveInstanceMethod:sel];
}
@end
//方法三
@implementation XZdog
void c_other(id self ,SEL _cmd){
NSLog(@"c_other:%@",NSStringFromSelector(_cmd));
}
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(test)) {
class_addMethod(self, sel, (IMP)c_other," [email protected]:8");
//表示有動態新增方法,這是規範,可以參考官方文件
return YES;
}
return [super resolveInstanceMethod:sel];
}
@end
我們到runtime原始碼的 objc-runtime-new.mm 類中看下 _class_lookupMethodAndLoadCache3 方法,以下我摘抄了部分原始碼。
retry: runtimeLock.assertReading(); // 類的快取 //快取中查詢方法 imp = cache_getImp(cls, sel); if (imp) goto done; { Method meth = getMethodNoSuper_nolock(cls, sel); if (meth) { log_and_fill_cache(cls, meth->imp, sel, inst, cls); imp = meth->imp; goto done; } } { unsigned attempts = unreasonableClassCount(); for (Class curClass = cls->superclass; curClass != nil; curClass = curClass->superclass) { if (--attempts == 0) { _objc_fatal("Memory corruption in class list."); } // 父類方法的快取 imp = cache_getImp(curClass, sel); if (imp) { if (imp != (IMP)_objc_msgForward_impcache) { //父類方法的快取列表 log_and_fill_cache(cls, imp, sel, inst, curClass); goto done; } else { break; } } // Superclass. Method meth = getMethodNoSuper_nolock(curClass, sel); if (meth) { log_and_fill_cache(cls, meth->imp, sel, inst, curClass); imp = meth->imp; goto done; } } } //訊息傳送者是否是第一次解析 //假如沒有實現動態新增 triedResolver = YES 進入retry //當triedResolver = YES 時,下次就進不了這個方法,直接進入 imp = (IMP)_objc_msgForward_impcache (訊息轉發) if (resolver && !triedResolver) { runtimeLock.unlockRead(); _class_resolveMethod(cls, sel, inst); runtimeLock.read(); triedResolver = YES; goto retry; } // (訊息轉發) imp = (IMP)_objc_msgForward_impcache; cache_fill(cls, sel, imp, inst); done: runtimeLock.unlockRead(); return imp; }
- 訊息轉發
main.m 呼叫XZBrother *b = [[XZBrother alloc]init];[b test];
列印結果:RuntimeTest[1033:83983] -[XZCat test] ,程式碼如下
@interface XZBrother : NSObject
-(void)test;
@end
#import "XZBrother.h"
#import "XZCat.h"
@implementation XZBrother
- (id)forwardingTargetForSelector:(SEL)aSelector{
if ([email protected](test)) {
return [[XZCat alloc]init];
}
return [super forwardingTargetForSelector:aSelector];
}
@end
@interface XZCat : NSObject
- (void)test;
@end
@implementation XZCat
- (void)test{
NSLog(@"-----");
NSLog(@"%s",__func__);
}
@end
由原始碼可知訊息轉發來了這個方法:imp = (IMP)_objc_msgForward_impcache;
全域性搜尋,最後在objc-msg-arm64.s 彙編中發現這個方法的實現
全域性搜尋下_objc_forward_handler 看下這個方法的實現, 你會發現你看的是下面這一塊
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;
然後就想 這都是列印資訊啊,沒錯,runtime的訊息轉發原始碼是不開源的,我們都知道當本類沒有實現方法的時候,訊息轉發機制會呼叫forwardingTargetForSelector方法,假如此時該方法返回的是nil或者沒有實現,又會發生什麼呢?從國外的大神編寫的訊息轉發的虛擬碼中可以看出,他會用methodSignatureForSelector:(SEL)aSelector方法。
- (id)forwardingTargetForSelector:(SEL)aSelector{
if ([email protected](test)) {
//返回值為nil 或者 forwardingTargetForSelector不實現的情況
return nil;
}
return [super forwardingTargetForSelector:aSelector];
}
//返回一個方法的簽名:返回值型別/引數型別
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
// 假如這個地方返回的是nil,代表你放棄了這個方法的處理,就會報經典的錯誤了,unrecognized selector sent to instance...
// return [NSMethodSignature signatureWithObjCTypes:"[email protected]:"];
return [NSMethodSignature signatureWithObjCTypes:"[email protected]:8"];
}
//NSInvocation封裝了方法的呼叫者,方法名,方法引數
//anInvocation.target 方法的呼叫者
//anInvocation.selector 方法名
//[anInvocation getArgument:NULL atIndex:0] 方法引數
//方法引數atIndex 這裡需要注意下還有兩個隱藏的引數。
- (void)forwardInvocation:(NSInvocation *)anInvocation{
[anInvocation invokeWithTarget:[[XZCat alloc]init]];
}
訊息轉發之後還是找不到方法的實現就會報最經典的錯誤 unrecognized selector sent to instance...
以上舉例都在物件方法,下面咱們來看看類方法。
@interface XZCat : NSObject
+ (void)eat:(int)cut;
@end
#import "XZCat.h"
@implementation XZCat
+(void)eat:(int)cut{
NSLog(@"%s",__func__);
}
@end
@interface XZBrother : NSObject
+ (void)eat:(int)cut;
@end
#import "XZBrother.h"
#import "XZCat.h"
@implementation XZBrother
//從老外的原始碼可以看出此方法是訊息轉發者直接呼叫,類方法的reveiver就是類本身。所以
//forwardingTargetForSelector 前面要寫上 加好+
//但是這個方法是沒有智慧提示的,沒關係,可以先調出物件方法,手動吧前面的- 改成+ 就OK了。
//同理如果forwardingTargetForSelector返回的是nil時,methodSignatureForSelector 和forwardInvocation 都要吧他改成類方法
+ (id)forwardingTargetForSelector:(SEL)aSelector{
if ([email protected](eat:)) {
//objc_msgsend([XZBrother class], aSelector)
return [XZBrother class];
}
return [super forwardingTargetForSelector:aSelector];
}
@end