前言
前邊兩篇文章(objc_msgSend探索、消息的查找流程探索)我們對(duì)調(diào)用方法到消息的查找流程做了詳細(xì)探索,如果說我們沒有找到方法(消息)系統(tǒng)是怎么處理的,我們又該做些什么去防止崩潰呢。這篇文章我們就對(duì)動(dòng)態(tài)方法解析和消息的轉(zhuǎn)發(fā)機(jī)制進(jìn)行詳細(xì)探索研究。
一、動(dòng)態(tài)方法解析
消息的查找流程探索文章我們探索到了方法lookUpImpOrForward尋找方法imp,當(dāng)遍歷完superclass沒有找到的時(shí)候,會(huì)進(jìn)入_class_resolveMethod動(dòng)態(tài)方法解析過程,如下:
if (resolver && !triedResolver) {
runtimeLock.unlock();
//動(dòng)態(tài)方法解析入口
_class_resolveMethod(cls, sel, inst);
runtimeLock.lock();
// Don't cache the result; we don't hold the lock so it may have
// changed already. Re-do the search from scratch instead.
triedResolver = YES;
goto retry;
}
_class_resolveMethod方法如下:
void _class_resolveMethod(Class cls, SEL sel, id inst)
{
if (! cls->isMetaClass()) {
// try [cls resolveInstanceMethod:sel]
//對(duì)象方法存在于類的方法列表里,類屬于非元類class
_class_resolveInstanceMethod(cls, sel, inst);
}
else {
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
//類方法存在于元類的方法列表里,元類的class
_class_resolveClassMethod(cls, sel, inst);
if (!lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
_class_resolveInstanceMethod(cls, sel, inst);
}
}
}
- 對(duì)象方法
可以看出如果是對(duì)象方法會(huì)調(diào)用_class_resolveInstanceMethod方法,cls是類,詳情如下:
static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst)
{
//尋找一下+resolveInstanceMethod的imp,如果找不到直接return
//找不到的話下方調(diào)用+resolveInstanceMethod會(huì)崩潰
if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
// Resolver not implemented.
return;
}
//給objc_msgSend多添加一個(gè)參數(shù),通過匯編快速尋找方法imp
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 doesn't fire next time.
// +resolveInstanceMethod adds to self a.k.a. cls
//異常處理
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));
}
}
}
1、SEL_resolveInstanceMethod就是NSObject的+resolveInstanceMethod,首先會(huì)判斷一下+resolveInstanceMethod方法是否實(shí)現(xiàn),如果沒實(shí)現(xiàn)直接return,要不然下邊調(diào)用會(huì)崩潰。
2、然后通過objc_msgSend匯編快速查找對(duì)象方法sel,動(dòng)態(tài)方法解析完成后,會(huì)goto retry;重新查找一遍imp,動(dòng)態(tài)解析標(biāo)識(shí)triedResolver變?yōu)?code>YES。cls是類。
3、如果還是沒有查找到會(huì)進(jìn)入_objc_msgForward_impcache消息轉(zhuǎn)發(fā),轉(zhuǎn)發(fā)不成功就會(huì)崩潰。
- 類方法
如果是類方法的時(shí)候調(diào)用_class_resolveClassMethod方法,cls是元類,詳情如下:
static void _class_resolveClassMethod(Class cls, SEL sel, id inst)
{
assert(cls->isMetaClass());
//尋找一下+resolveClassMethod的imp,如果找不到直接return
//找不到的話下方調(diào)用+resolveClassMethod會(huì)崩潰
if (! lookUpImpOrNil(cls, SEL_resolveClassMethod, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
// Resolver not implemented.
return;
}
//給objc_msgSend多添加一個(gè)參數(shù),通過匯編快速尋找方法imp
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
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));
}
}
}
1、SEL_resolveClassMethod就是NSObject的+ resolveClassMethod,首先會(huì)判斷一下+ resolveClassMethod方法是否實(shí)現(xiàn),如果沒實(shí)現(xiàn)直接return,要不然下邊調(diào)用會(huì)崩潰。
2、然后通過objc_msgSend匯編快速查找類方法sel,獲取到cls元類的nonMetaClass非元類,動(dòng)態(tài)方法解析完成后,會(huì)goto retry;重新查找一遍imp,動(dòng)態(tài)解析標(biāo)識(shí)triedResolver變?yōu)?code>YES。cls是元類。
3、如果還是沒有查找到會(huì)進(jìn)入_objc_msgForward_impcache消息轉(zhuǎn)發(fā),轉(zhuǎn)發(fā)不成功就會(huì)崩潰。
為什么要獲取
cls元類的nonMetaClass非元類呢?
是因?yàn)殚_發(fā)者是不能在元類中重寫和自定義方法+ resolveClassMethod,所以objc_msgSend要向類發(fā)送消息。
- 防崩潰處理
+resolveInstanceMethod和+resolveClassMethod的實(shí)現(xiàn)如下:
+ (BOOL)resolveClassMethod:(SEL)sel {
return NO;
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
return NO;
}
這兩個(gè)方法是NSObject的類方法,正常不處理的話return NO,開發(fā)者不實(shí)現(xiàn)方法就會(huì)找不到,然后崩潰。那么我們可以重寫該方法然后把未實(shí)現(xiàn)的方法動(dòng)態(tài)添加到類上,return YES,那么是不是就能夠防止崩潰了。接下來我們驗(yàn)證一下。
我們創(chuàng)建兩個(gè)類LGPerson和LGStudent,LGStudent繼承于LGPerson,LGPerson繼承于NSObject,LGPerson聲明兩個(gè)方法-(void)saySomething和+(void)sayLove,但是不實(shí)現(xiàn)。LGStudent聲明并實(shí)現(xiàn)-(void)sayHello和+(void)sayObjc。如下圖:



在main.m中用LGStudent類分別調(diào)用-(void)saySomething或者+(void)sayLove,都會(huì)崩潰unrecognized selector sent to instance xxxxxx。接下來我們來做一些處理。
首先我們來驗(yàn)證一下對(duì)象方法resolveInstanceMethod,在LGStudent.m重寫resolveInstanceMethod方法,判斷saySomething方法,如下:
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
if (sel == @selector(saySomething))
{
IMP helloImp = class_getMethodImplementation(self, @selector(sayHello));
Method helloMethod = class_getClassMethod(self, @selector(sayHello));
const char *helloTypes = method_getTypeEncoding(helloMethod);
return class_addMethod(self, sel, helloImp, helloTypes);
}
return [super resolveInstanceMethod:sel];
}
判斷是saySomething方法的時(shí)候,動(dòng)態(tài)添加對(duì)象方法saySomething,把saySomething的sel的實(shí)現(xiàn)指向了sayHello的imp,當(dāng)調(diào)用的時(shí)候會(huì)執(zhí)行sayHello的imp,打印-[LGStudent sayHello]。
然后我們來以相同的方式驗(yàn)證一下resolveClassMethod,在LGStudent.m重寫resolveClassMethod方法,判斷sayLove方法,如下:
+ (BOOL)resolveClassMethod:(SEL)sel
{
if (sel == @selector(sayLove))
{
IMP objcImp = class_getMethodImplementation(objc_getMetaClass("LGStudent"), @selector(sayObjc));
Method objcMethod = class_getClassMethod(objc_getMetaClass("LGStudent"), @selector(sayObjc));
const char *objcTypes = method_getTypeEncoding(objcMethod);
return class_addMethod(objc_getMetaClass("LGStudent"), sel, objcImp, objcTypes);
}
return [super resolveClassMethod:sel];
}
這里要注意一點(diǎn)類方法是存儲(chǔ)在元類里的,所以動(dòng)態(tài)添加方法要添加到元類里,方法的imp和types都要從元類里獲取。動(dòng)態(tài)添加類方法sayLove,把sayLove的實(shí)現(xiàn)指向sayObjc的imp,當(dāng)調(diào)用的時(shí)候會(huì)執(zhí)行sayObjc的imp,打印+[LGStudent sayObjc]。
- 問題:當(dāng)類方法調(diào)用
_class_resolveMethod的時(shí)候,cls是元類,那為什么先調(diào)用了_class_resolveClassMethod,然后!lookUpImpOrNil判斷之后又調(diào)用了_class_resolveInstanceMethod呢?
首先看一下lookUpImpOrNil的實(shí)現(xiàn):
IMP lookUpImpOrNil(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
IMP imp = lookUpImpOrForward(cls, sel, inst, initialize, cache, resolver);
if (imp == _objc_msgForward_impcache) return nil;
else return imp;
}
lookUpImpOrForward是尋找方法的imp,上篇文章中有詳細(xì)介紹。
解答:
_class_resolveClassMethod會(huì)通過objc_msgSend向cls的非元類發(fā)送一個(gè)消息,如果+ resolveClassMethod對(duì)sel做了處理,那么就能查找出imp。如果未查找到
imp,調(diào)用_class_resolveInstanceMethod,會(huì)通過objc_msgSend向cls元類發(fā)送一個(gè)消息,如果+ resolveInstanceMethod對(duì)sel做了處理,那么就能查找出imp。否則崩潰。注:這其中主要還是
isa的走位,根元類的父類還是NSObject類,元類的superclass鏈會(huì)查找到NSObject類中。如果NSObject類中重寫并實(shí)現(xiàn)了sel。
二、消息轉(zhuǎn)發(fā)機(jī)制
當(dāng)動(dòng)態(tài)方法解析失敗的時(shí)候就進(jìn)入到了消息轉(zhuǎn)發(fā)過程。OC的消息轉(zhuǎn)發(fā)機(jī)制就是當(dāng)一個(gè)對(duì)象的方法調(diào)用(消息發(fā)送)它本身無法處理的時(shí)候,為了防止崩潰報(bào)錯(cuò)可以把消息轉(zhuǎn)發(fā)個(gè)另一個(gè)對(duì)象處理,這就叫做消息轉(zhuǎn)發(fā)機(jī)制。消息的查找流程探索中我們提到了當(dāng)未找到sel的imp的時(shí)候會(huì)進(jìn)入_objc_msgForward_impcache進(jìn)入?yún)R編消息轉(zhuǎn)發(fā)。那么我們應(yīng)該怎么跟蹤并嘗試消息轉(zhuǎn)發(fā)過程呢?
消息查找流程的時(shí)候我們提到了一個(gè)方法log_and_fill_cache,是用來緩存方法的,他還有一個(gè)日志緩存的方法logMessageSend,詳情如下:
log_and_fill_cache(Class cls, IMP imp, SEL sel, id receiver, Class implementer)
{
#if SUPPORT_MESSAGE_LOGGING
if (objcMsgLogEnabled) {
bool cacheIt = logMessageSend(implementer->isMetaClass(),
cls->nameForLogging(),
implementer->nameForLogging(),
sel);
if (!cacheIt) return;
}
#endif
//方法緩存
cache_fill (cls, sel, imp, receiver);
}
bool logMessageSend(bool isClassMethod,
const char *objectsClass,
const char *implementingClass,
SEL selector)
{
char buf[ 1024 ];
// Create/open the log file
if (objcMsgLogFD == (-1))
{
//緩存位置
snprintf (buf, sizeof(buf), "/tmp/msgSends-%d", (int) getpid ());
objcMsgLogFD = secure_open (buf, O_WRONLY | O_CREAT, geteuid());
if (objcMsgLogFD < 0) {
// no log file - disable logging
objcMsgLogEnabled = false;
objcMsgLogFD = -1;
return true;
}
}
// Make the log entry
snprintf(buf, sizeof(buf), "%c %s %s %s\n",
isClassMethod ? '+' : '-',
objectsClass,
implementingClass,
sel_getName(selector));
objcMsgLogLock.lock();
write (objcMsgLogFD, buf, strlen(buf));
objcMsgLogLock.unlock();
// Tell caller to not cache the method
return false;
}
可以看出緩存的文件位置在/tmp/msgSends,objcMsgLogEnabled是個(gè)布爾值,控制objcMsgLogEnabled值的方法是void instrumentObjcMessageSends(BOOL flag),這樣我們可以嘗試著把消息轉(zhuǎn)發(fā)過程中的方法緩存一下,看看都調(diào)用了哪些方法。
首先我們?cè)?code>main.m文件中添加方法extern void instrumentObjcMessageSends(BOOL);,然后在調(diào)用未實(shí)現(xiàn)方法前后調(diào)用此方法,如下:
int main(int argc, const char * argv[]) {
@autoreleasepool {
LGStudent *student = [[LGStudent alloc] init];
instrumentObjcMessageSends(YES);
[student saySomething];
instrumentObjcMessageSends(NO);
}
return 0;
}
然后運(yùn)行項(xiàng)目command+shift+G尋找到到日志文件,下邊是復(fù)制了部分日志方法,如下:
+ LGStudent NSObject resolveInstanceMethod:
+ LGStudent NSObject resolveInstanceMethod:
- LGStudent NSObject forwardingTargetForSelector:
- LGStudent NSObject forwardingTargetForSelector:
- LGStudent NSObject methodSignatureForSelector:
- LGStudent NSObject methodSignatureForSelector:
- LGStudent NSObject class
+ LGStudent NSObject resolveInstanceMethod:
+ LGStudent NSObject resolveInstanceMethod:
- LGStudent NSObject doesNotRecognizeSelector:
- LGStudent NSObject doesNotRecognizeSelector:
- LGStudent NSObject class
//下邊的省略
全局找不到這些方法,那我們就看一下文檔這些方法是干什么的。翻譯如下:
forwardingTargetForSelector:
1、如果對(duì)象實(shí)現(xiàn)(或繼承)這個(gè)方法,并且返回了非
nil和非self結(jié)果,那么這個(gè)被返回的對(duì)象將作為新的接收消息的對(duì)象并且消息會(huì)發(fā)送到這個(gè)新對(duì)象。(如果在這個(gè)方法里返回了self,那么代碼就會(huì)進(jìn)入到無限循環(huán)里。)。
2、如果在非根類里實(shí)現(xiàn)了這個(gè)方法,并且對(duì)于selector沒有什么可以返回,需要返回super的實(shí)現(xiàn)。
3、在更復(fù)雜的forwardInvocation:執(zhí)行之前,這個(gè)方法給了對(duì)象一個(gè)重新向它發(fā)送未知消息的機(jī)會(huì)。當(dāng)你只是簡(jiǎn)單地把消息重新定向到另外一個(gè)對(duì)象的時(shí)候這個(gè)方法很有用,比常規(guī)的轉(zhuǎn)發(fā)快了一個(gè)量級(jí)。但是當(dāng)你轉(zhuǎn)發(fā)的目標(biāo)是捕獲NSInvocation,或者是在轉(zhuǎn)發(fā)過程中有參數(shù)或返回值操作,這個(gè)方法就沒什么用了。
methodSignatureForSelector :
在必要的時(shí)候必須要避免無限循環(huán),方法是檢查
aSelector是不是該方法的selector和不發(fā)送任何可能調(diào)起該方法的消息。
doesNotRecognizeSelector :
1、每當(dāng)對(duì)象接收到它無法響應(yīng)或轉(zhuǎn)發(fā)的
aSelector消息時(shí),運(yùn)行時(shí)系統(tǒng)就會(huì)調(diào)用此方法。此方法反過來引發(fā)NSInvalidArgumentException,并生成錯(cuò)誤消息。
2、任何DoesNotRecogniteSelector:消息通常僅由運(yùn)行時(shí)系統(tǒng)發(fā)送。但是,可以在程序代碼中使用它們來防止方法被繼承。例如,NSObject子類可以通過重新實(shí)現(xiàn)copy或init方法來不使用父類copy或init方法,重寫的方法里要包含DoesNotRecogniteSelector:,如下:- (id)copy { [self doesNotRecognizeSelector:_cmd]; }3、如果重寫此方法,則必須在實(shí)現(xiàn)結(jié)束時(shí)調(diào)用
super或引發(fā)NSInvalidArgumentException異常。換句話說,此方法不能正常返回;它必須始終導(dǎo)致引發(fā)異常。
-
forwardInvocation:
該方法并沒有記錄在日志文件中,是因?yàn)榱鞒虥]有走到它這一步就報(bào)錯(cuò)了。
1、當(dāng)一個(gè)對(duì)象發(fā)送一個(gè)未實(shí)現(xiàn)的方法的時(shí)候,運(yùn)行時(shí)系統(tǒng)會(huì)給他一個(gè)把消息委托給另一個(gè)對(duì)象的機(jī)會(huì)。運(yùn)行時(shí)系統(tǒng)會(huì)通過創(chuàng)建一個(gè)代表消息的
NSInvocation對(duì)象并且會(huì)向接收者發(fā)送一個(gè)包含NSInvocation作為參數(shù)的forwardInvocation:消息。(如果該對(duì)象也無法響應(yīng)消息,它也有機(jī)會(huì)轉(zhuǎn)發(fā)該消息。)
2、forwardInvocation:允許消息發(fā)送對(duì)象與其他對(duì)象建立關(guān)系,對(duì)于某些消息,這些對(duì)象將代表消息發(fā)送對(duì)象執(zhí)行消息。在某種意義上,這些對(duì)象能夠“繼承”消息發(fā)送對(duì)象的某些特性。
3、methodSignatureForSelector :和forwardInvocation:必須都要實(shí)現(xiàn)才能起作用。第一個(gè)方法生成方法簽名并返回,當(dāng)簽名正確并且不為nil,第二個(gè)方法才會(huì)生成一個(gè)NSInvocation。
看一下消息轉(zhuǎn)發(fā)的流程圖,如下圖:

可以看出動(dòng)態(tài)方法解析失敗的時(shí)候,還會(huì)有兩次消息轉(zhuǎn)發(fā)的步驟,快速流程和慢速流程。
快速流程
當(dāng)動(dòng)態(tài)方法解析失敗的時(shí)候,可以通過forwardingTargetForSelector:快速指定新的消息接收對(duì)象,這就是快速流程。慢速流程
當(dāng)快速流程forwardingTargetForSelector:返回值為nil的時(shí)候,會(huì)調(diào)用methodSignatureForSelector :返回方法簽名,然后調(diào)用forwardInvocation:生成NSInvocation,可以在此方法中做一些對(duì)未實(shí)現(xiàn)消息的處理。
- 快速流程實(shí)現(xiàn)
LGStudent調(diào)用未實(shí)現(xiàn)方法saySomething,創(chuàng)建LGTeacher類實(shí)現(xiàn)此方法,如下:
@interface LGTeacher : NSObject
@end
@implementation LGTeacher
- (void)saySomething{
NSLog(@"%s",__func__);
}
@end
實(shí)現(xiàn)forwardingTargetForSelector:如下:
- (id)forwardingTargetForSelector:(SEL)aSelector{
NSLog(@"%s -- %@",__func__,NSStringFromSelector(aSelector));
if (aSelector == @selector(saySomething)) {
return [LGTeacher alloc];
}
return [super forwardingTargetForSelector:aSelector];
}
運(yùn)行起來控制臺(tái)打印結(jié)果如下:
2020-02-14 15:26:40.711832+0800 008-方法查找-消息轉(zhuǎn)發(fā)[3038:77509] -[LGStudent forwardingTargetForSelector:] -- saySomething
2020-02-14 15:26:40.713167+0800 008-方法查找-消息轉(zhuǎn)發(fā)[3038:77509] -[LGTeacher saySomething]
Program ended with exit code: 0
可以看出,- (id)forwardingTargetForSelector:(SEL)aSelector方法把saySomething這個(gè)消息轉(zhuǎn)發(fā)給了LGTeacher對(duì)象,調(diào)用了LGTeacher類中的saySomething實(shí)現(xiàn)。
- 慢速流程實(shí)現(xiàn)
LGStudent調(diào)用未實(shí)現(xiàn)方法saySomething,創(chuàng)建LGTeacher類實(shí)現(xiàn)此方法,實(shí)現(xiàn)methodSignatureForSelector :返回一個(gè)非空的方法簽名,實(shí)現(xiàn)forwardInvocation :做一些處理(也可以不做),如下:
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
NSLog(@"%s -- %@",__func__,NSStringFromSelector(aSelector));
if (aSelector == @selector(saySomething)) { // v @ :
//返回一個(gè)非空的方法簽名
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
NSLog(@"%s ",__func__);
//什么都不處理就行,實(shí)現(xiàn)此方法可以防止崩潰
//可以不指定接收對(duì)象
SEL aSelector = [anInvocation selector];
if ([[LGTeacher alloc] respondsToSelector:aSelector])
[anInvocation invokeWithTarget:[LGTeacher alloc]];
else
[super forwardInvocation:anInvocation];
}
運(yùn)行控制臺(tái)輸出如下:
2020-02-14 15:41:08.681776+0800 008-方法查找-消息轉(zhuǎn)發(fā)[3181:84329] -[LGStudent methodSignatureForSelector:] -- saySomething
2020-02-14 15:41:08.682382+0800 008-方法查找-消息轉(zhuǎn)發(fā)[3181:84329] -[LGStudent forwardInvocation:]
2020-02-14 15:41:08.682786+0800 008-方法查找-消息轉(zhuǎn)發(fā)[3181:84329] -[LGTeacher saySomething]
Program ended with exit code: 0
總結(jié)
運(yùn)行時(shí)對(duì)于未實(shí)現(xiàn)的方法的處理有三種:
1、動(dòng)態(tài)方法解析
通過重寫+resolveInstanceMethod(對(duì)象方法)或者+resolveClassMethod(類方法)動(dòng)態(tài)的添加方法imp,再次通過lookUpImpOrForward的goto retry;查找一遍,找到imp。找不到進(jìn)入消息轉(zhuǎn)發(fā)流程。
2、消息轉(zhuǎn)發(fā)機(jī)制
-
forwardingTargetForSelector:快速指定非nil和非self的一個(gè)對(duì)象,把消息轉(zhuǎn)發(fā)給這個(gè)對(duì)象。 - 當(dāng)快速流程
forwardingTargetForSelector:返回值為nil的時(shí)候,會(huì)調(diào)用methodSignatureForSelector :返回方法簽名,然后調(diào)用forwardInvocation:生成NSInvocation,可以在此方法中做一些對(duì)未實(shí)現(xiàn)消息的處理。