[obj message]調(diào)用執(zhí)行過程如下圖:
一個函數(shù)是由一個selector(SEL),和一個implement(IML)組成的汪疮。Selector相當于門牌號峭火,而Implement才是真正的住戶(函數(shù)實現(xiàn))。
一個OC對象執(zhí)行[obj message]方法后智嚷,如果message并沒有被obj所屬類實現(xiàn)卖丸,會報unrecognized selector sent to instance
錯誤,runtime給了我們3次機會去避免發(fā)生這樣的情況盏道。
1.OC對象在收到無法解讀的消息后稍浆,首先會調(diào)用所屬類的+ (BOOL)resolveInstanceMethod:(SEL)sel
,這個方法在運行時,沒有找到SEL的IML時就會執(zhí)行猜嘱。這個函數(shù)是給實例對象利用class_addMethod添加函數(shù)的機會衅枫。根據(jù)文檔,如果實現(xiàn)了添加函數(shù)代碼則返回YES朗伶,未實現(xiàn)返回NO弦撩。resolveClassMethod:
是用于動態(tài)解析一個類方法;而resolveInstanceMethod:
是用于動態(tài)解析一個實例方法论皆。
//類函數(shù)
+ (BOOL) resolveInstanceMethod:(SEL)aSEL
{
if (aSEL == @selector(resolveThisMethodDynamically))
{
class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:");
return YES;
}
return [super resolveInstanceMethod:aSel];
}```
關于class_addMethod這個方法益楼,是這樣定義的
OBJC_EXPORT BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
cls 在這個類中添加方法猾漫,也就是方法所添加的類
name 方法名,這個可以隨便起的
imp 實現(xiàn)這個方法的函數(shù)
types 定義該數(shù)返回值類型和參數(shù)類型的字符串感凤,這里比如"v@:"悯周,其中v就是void,帶表返回類型就是空陪竿,@代表參數(shù)禽翼,這里指的是id(self),這里:指的是方法SEL(_cmd)族跛,比如再定義一個函數(shù)
int newMethod (id self, SEL _cmd, NSString *str) {
return 100;
}
那么添加這個函數(shù)的方法就應該是ass_addMethod([self class], @selector(newMethod), (IMP)newMethod, "i@:@");
2.如果在+ (BOOL)resolveInstanceMethod:(SEL)sel中沒有找到或者添加方法闰挡,消息繼續(xù)往下傳遞到`- (id)forwardingTargetForSelector:(SEL)aSelector`看看是不是有對象可以執(zhí)行這個方法,該方法返回未被接收消息最先被轉(zhuǎn)發(fā)到的對象庸蔼。如果一個對象實現(xiàn)了這個方法,并返回一個非空的對象(且非對象本身)贮匕,則這個被返回的對象成為消息的新接收者姐仅。另外如果在非根類里面實現(xiàn)這個方法,如果對于給定的selector刻盐,我們沒有可用的對象可以返回掏膏,則應該調(diào)用父類的方法實現(xiàn),并返回其結果敦锌。
//將消息轉(zhuǎn)出某對象
-
(id)forwardingTargetForSelector:(SEL)aSelector
{
NSLog(@"MyTestObject _cmd: %@", NSStringFromSelector(_cmd));NoneClass *none = [[NoneClass alloc] init];
if ([none respondsToSelector: aSelector]) {
return none;
}return [super forwardingTargetForSelector: aSelector];
}
3.當前面兩步都無法處理消息時馒疹,運行時系統(tǒng)便會給接收者最后一個機會,將其轉(zhuǎn)發(fā)給其它代理對象來處理乙墙。這主要是通過創(chuàng)建一個表示消息的`NSInvocation`對象并將這個對象當作參數(shù)傳遞給`forwardInvocation:`方法颖变。我們在`forwardInvocation:`方法中可以選擇將消息轉(zhuǎn)發(fā)給其它對象。
在這個方法中听想,主要是需要做兩件事:
(1).找到一個能處理anInvocation調(diào)用的對象腥刹。
(2).將消息以anInvocation的形式發(fā)送給對象。anInvocation將維護調(diào)用的結果汉买,而運行時則會將這個結果返回給消息的原始發(fā)送者衔峰。
真正執(zhí)行從 `methodSignatureForSelector:`返回的NSMethodSignature。這個函數(shù)讓重載方有機會拋出一個函數(shù)的簽名蛙粘,再由后面的forwardInvocation:去執(zhí)行垫卤。在這個函數(shù)里可以將NSInvocation多次轉(zhuǎn)發(fā)到多個對象中,這也是這種方式靈活的地方出牧。(forwardingTargetForSelector只能以Selector的形式轉(zhuǎn)向一個對象)
(NSMethodSignature *)methodSignatureForSelector:(SEL)selector
{
NSString *sel = NSStringFromSelector(selector);
if ([sel rangeOfString:@"set"].location == 0) {
//動態(tài)造一個 setter函數(shù)
return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
} else {
//動態(tài)造一個 getter函數(shù)
return [NSMethodSignature signatureWithObjCTypes:"@@:"];
}
}(void)forwardInvocation:(NSInvocation *)invocation
{
//拿到函數(shù)名
NSString *key = NSStringFromSelector([invocation selector]);
if ([key rangeOfString:@"set"].location == 0) {
//setter函數(shù)形如 setXXX: 拆掉 set和冒號
key = [[key substringWithRange:NSMakeRange(3, [key length]-4)] lowercaseString];
NSString *obj;
//從參數(shù)列表中找到值
[invocation getArgument:&obj atIndex:2];
[data setObject:obj forKey:key];
} else {
//getter函數(shù)就相對簡單了穴肘,直接把函數(shù)名做 key就好了。
NSString *obj = [data objectForKey:key];
[invocation setReturnValue:&obj];
}
}
`- (void)doesNotRecognizeSelector:(SEL)aSelector`
作為找不到函數(shù)實現(xiàn)的最后一步舔痕,NSObject實現(xiàn)這個函數(shù)只有一個功能梢褐,就是拋出異常旺遮。
雖然理論上可以重載這個函數(shù)實現(xiàn)保證不拋出異常(不調(diào)用super實現(xiàn)),但是蘋果文檔著重提出“一定不能讓這個函數(shù)就這么結束掉盈咳,必須拋出異彻⒚迹”。
#使用場景:
在一個函數(shù)找不到時鱼响,Objective-C提供了三種方式去補救:
1鸣剪、調(diào)用resolveInstanceMethod給個機會讓類添加這個實現(xiàn)這個函數(shù)
2、調(diào)用forwardingTargetForSelector讓別的對象去執(zhí)行這個函數(shù)
3丈积、調(diào)用methodSignatureForSelector(函數(shù)符號制造器)和forwardInvocation(函數(shù)執(zhí)行器)靈活的將目標函數(shù)以其他形式執(zhí)行筐骇。
如果都不中,調(diào)用doesNotRecognizeSelector拋出異常江滨。
參考:
http://www.cocoachina.com/ios/20160302/15494.html
http://www.cnblogs.com/biosli/p/NSObject_inherit_2.html
http://www.cocoachina.com/ios/20150205/11113.html