iOS開(kāi)發(fā)過(guò)程中,有一類的錯(cuò)誤會(huì)經(jīng)常遇到,就是找不到所調(diào)用的方法,當(dāng)然這類問(wèn)題比較好解決,給當(dāng)前對(duì)象或其父類對(duì)象添加該方法即可,使得編譯器在編譯時(shí)能正確找到該方法;或者,還有另外的方法,由于Objective-C是一門動(dòng)態(tài)語(yǔ)言,我們也可以在運(yùn)行期再給類添加該方法,一樣可以解決該問(wèn)題,而這就涉及了類的消息轉(zhuǎn)發(fā)機(jī)制。
消息轉(zhuǎn)發(fā)到底是什么呢? 這里將分為三個(gè)部分進(jìn)行逐一講解:
1、動(dòng)態(tài)方法解析
2、備用接收者
3、完整消息轉(zhuǎn)發(fā)

1.動(dòng)態(tài)方法解析
首先,Objective-C運(yùn)行時(shí)會(huì)調(diào)用+resolveInstanceMethod:或者+resolveClassMethod:,讓你有機(jī)會(huì)提供一個(gè)函數(shù)實(shí)現(xiàn)。
如果你添加了函數(shù)并返回YES, 那運(yùn)行時(shí)系統(tǒng)就會(huì)重新啟動(dòng)一次消息發(fā)送的過(guò)程。
+(BOOL)resolveInstanceMethod:(SEL)sel{
NSLog(@"未實(shí)現(xiàn)的調(diào)用方法 = %@",NSStringFromSelector(sel));
IMP class = class_getMethodImplementation(self, @selector(functionOne));
class_addMethod(self, sel, class, "v@:");
return true;
}
-(void)functionOne{
NSLog(@"這是方法一");
}
我們可以在resolveInstanceMethod 中添加方法并返回YES,上例中我添加了functionOne并成功執(zhí)行了,如果未添加方法實(shí)現(xiàn),則返回false,切記不能在該方法中添加未實(shí)現(xiàn)的方法,否則會(huì)進(jìn)入死循環(huán)。(實(shí)踐中如果添加了方法,無(wú)論返回ture或者false都會(huì)執(zhí)行添加的方法,不會(huì)進(jìn)入消息轉(zhuǎn)發(fā),返回true或false似乎沒(méi)什么影響,不過(guò)還是按照官方文檔來(lái)做吧)。那么消息將進(jìn)入到消息轉(zhuǎn)發(fā)forwardingTargetForSelector中。
添加方法中應(yīng)該看到了v@:,這是定義方法的,我會(huì)寫一篇文章專門講解這個(gè)。
2.備用接收者
如果我們?cè)趓esolveInstanceMethod沒(méi)有執(zhí)行添加的方法,并且返回了false,那么消息將會(huì)轉(zhuǎn)發(fā)到這里。
///消息轉(zhuǎn)發(fā),如果在這里轉(zhuǎn)發(fā)只可轉(zhuǎn)發(fā)給一個(gè)對(duì)象或一個(gè)方法
- (id)forwardingTargetForSelector:(SEL)aSelector{
NSLog(@"消息轉(zhuǎn)發(fā)");
// return self.control;
return nil;
}
我們需要傳遞一個(gè)對(duì)象來(lái)接收這個(gè)方法信息。分為兩種:
1.是傳遞方法的接收者,那么消息將傳遞給該對(duì)象并查找對(duì)應(yīng)的方法,我們定義的方法名稱和參數(shù)個(gè)數(shù)必須相同。返回值可以不同。
2.如果傳為nil,那么消息將進(jìn)入到完整消息轉(zhuǎn)發(fā)中。
3.完整消息轉(zhuǎn)發(fā)
如果在上一步還不能處理未知消息,則唯一能做的就是啟用完整的消息轉(zhuǎn)發(fā)機(jī)制了。
消息轉(zhuǎn)發(fā)分為兩部分
//注冊(cè)方法
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
/// 消息轉(zhuǎn)發(fā),可轉(zhuǎn)發(fā)給多個(gè)對(duì)象,或者多個(gè)方法同時(shí)執(zhí)行
- (void)forwardInvocation:(NSInvocation *)anInvocation;
首先它會(huì)發(fā)送-methodSignatureForSelector:消息獲得函數(shù)的參數(shù)和返回值類型。如果methodSignatureForSelector:返回nil,Runtime則會(huì)發(fā)出-doesNotRecognizeSelector:消息,程序這時(shí)也就掛掉了。
如果返回了一個(gè)函數(shù)簽名,Runtime就會(huì)創(chuàng)建一個(gè)NSInvocation對(duì)象并發(fā)送-forwardInvocation:消息給目標(biāo)對(duì)象
///注冊(cè)方法
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
return [NSMethodSignature signatureWithObjCTypes:"#@:#"];
}
這里我定義的參數(shù)簽名為"#@:#",接收一個(gè)NSString類型的參數(shù),返回一個(gè)NSString類型的返回值。
/// 消息轉(zhuǎn)發(fā),可轉(zhuǎn)發(fā)給多個(gè)對(duì)象,或者多個(gè)方法同時(shí)執(zhí)行
- (void)forwardInvocation:(NSInvocation *)anInvocation{
NSLog(@"%@",anInvocation);
[anInvocation invokeWithTarget:self.control];
}
這里我們將消息轉(zhuǎn)為控制器對(duì)象去接收處理。
NSString * value = [person performSelector:@selector(run:) withObject:@"這是參數(shù)"];
NSLog(@"返回值 = %@",value);
-(NSString * )run:(NSString * )value{
NSLog(@"轉(zhuǎn)發(fā)給控制器調(diào)用 %@ %s",value,__FUNCTION__);
return @"這是返回值";
}
這里可以看到結(jié)果
RunTime-iOS[27980:1126582] 轉(zhuǎn)發(fā)給控制器調(diào)用 這是參數(shù) -[ViewController run:]
RunTime-iOS[27980:1126582] 返回值 = 這是返回值
控制器方法可以接收方法參數(shù),也可以返回參數(shù)給最初的調(diào)用者。