iOS - 消息轉(zhuǎn)發(fā)機(jī)制

我們知道,OC是動(dòng)態(tài)語言,所有的方法都會(huì)以消息的形式傳遞給對象,對象會(huì)根據(jù)方法的類型來進(jìn)行實(shí)例方法或者類方法的選擇,當(dāng)實(shí)例方法和類方法都不可以響應(yīng)這個(gè)消息時(shí),程序并不會(huì)立即報(bào)錯(cuò),而是啟動(dòng)消息轉(zhuǎn)發(fā)機(jī)制來處理這個(gè)消息,當(dāng)消息轉(zhuǎn)發(fā)機(jī)制都無法處理這個(gè)消息時(shí),程序才會(huì)crash,并提示“unrecognized selector sent to instance ****” 本篇文章主要是詳細(xì)記錄當(dāng)對象找不到對應(yīng)的方法時(shí),消息轉(zhuǎn)發(fā)的具體機(jī)制
具體分為3部分:


屏幕快照 2017-08-16 14.19.41.png

1、動(dòng)態(tài)方法解析

+ (BOOL)resolveInstanceMethod:(SEL)sel OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);
+ (BOOL)resolveClassMethod:(SEL)sel OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);

這兩個(gè)方法是消息轉(zhuǎn)發(fā)機(jī)制的第一步,分別對應(yīng)對象的類方法和實(shí)例方法,當(dāng)對象接收到不可處理的消息時(shí),會(huì)首先查閱此對象是否實(shí)現(xiàn)了對應(yīng)的對象方法或者類方法,在這個(gè)方法內(nèi),我們可以通過runtime動(dòng)態(tài)的添加對象的實(shí)例方法,從而使這個(gè)對象擁有響應(yīng)這個(gè)消息的能力

// Person.h
 @interface Person : NSObject
 - (void) eatFood:(NSString *)food;
 @end
  
// Person.m
- (void)personEatFood
{
    NSLog(@"person eat food");
}
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    if(sel == @selector(eatFood:)) {
        Method method = class_getInstanceMethod([self class], @selector(personEatFood));
        class_addMethod([self class], sel, method_getImplementation(method), "v@:");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}
  
 // ViewController.m
 id p = (id)[[Person alloc] init];
 [p eatFood:@"VCfood"];

上述代碼可以看出,在resolveInstanceMethod:方法中,我們動(dòng)態(tài)的為Person類添加了eatFood:方法,所以程序在運(yùn)行時(shí)沒有crash,而是正常的打印“person eat food” .


屏幕快照 2017-08-16 11.09.47.png

簡單說一下class_addMethod方法的含義:

BOOL class_addMethod(Class cls, SEL name, IMP imp,  const char *types) 

從名字可以很容易的看出,是為某個(gè)類添加方法的runtime函數(shù),它有四個(gè)參數(shù):
Class cls: 為哪個(gè)類添加方法
SEL name: 添加方法的名字
IMP imp:添加方法的內(nèi)容(這里是C函數(shù),可以用method_getImplementation直接獲取OC方法對應(yīng)的C函數(shù))
const char *type: type encoding 用來描述函數(shù)的參數(shù)和返回值 "v@:" v代表返回值為void ,@表示self,:表示selector,具體可參考官方介紹

2、備援接收者

如果對象沒有在第一步(動(dòng)態(tài)消息轉(zhuǎn)發(fā))中沒有動(dòng)態(tài)的添加可以響應(yīng)消息的方法,那runtime就會(huì)走到這一步,這一步,我們可以修改處理消息的對象,也就是,將這個(gè)消息轉(zhuǎn)給其他的對象處理

- (id)forwardingTargetForSelector:(SEL)aSelector OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);

這個(gè)方法返回一個(gè)對象(這個(gè)對象被稱為備援對象),然后runtime會(huì)將消息發(fā)給這個(gè)對象讓其處理,這一步需要注意的是,我們只可以改變接收消息的對象,但是無法改變消息本身(包括消息的名字以及參數(shù)等)

// 接著剛才的栗子,我們將resolveInstanceMethod:刪除,添加如下方法
//Person2.h
@interface Person2 : NSObject
- (void) eatFood:(NSString *) food;
@end
  
//Person.m
@implementation Person2
- (void)eatFood:(NSString *)food
{
    NSLog(@"person2 eat food ---- %@",food);
}
@end
  
//Person.m
- (instancetype) forwardingTargetForSelector:(SEL)aSelector
{
    if(aSelector == @selector(eatFood:)) {
        return [[Person2 alloc] init];
    }
    return [super forwardingTargetForSelector:aSelector];
}
  
// ViewController.m
 id p = (id)[[Person alloc] init];
 [p eatFood:@"VCfood"];

我們可以看到,Peson2這個(gè)對象里實(shí)現(xiàn)了和Person一樣的方法eatFood: 我們在forwardingTargetForSelector:方法里將消息的處理者從Person轉(zhuǎn)為了Person2,所以程序沒有crash,并輸出了正常打印


屏幕快照 2017-08-16 11.44.37.png

3、完整的消息轉(zhuǎn)發(fā)

所謂完整的消息轉(zhuǎn)發(fā)在于,系統(tǒng)會(huì)創(chuàng)建NSInvocation對象,里面包含消息的所有內(nèi)容(包括消息的名字,參數(shù)等),在這一過程,我們可以修改消息的內(nèi)容,并將它發(fā)送給目標(biāo)對象

//刪除掉Person.m的forwardingTargetForSelector: 方法 并添加如下方法
//Person.m
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    return [NSMethodSignature signatureWithObjCTypes:"v@:@@"]; //返回方法簽名,與最新的選擇子一致,本例中添加了一個(gè)參數(shù),所以簽名編程"v@:@@" :后的兩個(gè)@代表兩個(gè)參數(shù)
}
  
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    [anInvocation setSelector:@selector(personEat1:Eat2:)];
    NSString *eat2 = @"food2";
    [anInvocation setArgument:&eat2 atIndex:3];
    [anInvocation invokeWithTarget:self];
}
  
- (void) personEat1:(NSString *)eat1 Eat2:(NSString *)eat2
{
    NSLog(@"person eat food1=%@  ,   food2=%@",eat1,eat2);
}

可以看到,我們將選擇子(eatFood:)進(jìn)行了更新(personEat1:Eat2)同時(shí)添加了第二個(gè)參數(shù)(@"food2"),然后將消息又交給了self,這時(shí),因?yàn)閟elf實(shí)現(xiàn)了personEat1:Eat2: 方法,所以程序沒有crash,且有正常輸出


屏幕快照 2017-08-16 14.27.26.png

以上就是消息轉(zhuǎn)發(fā)的三個(gè)步驟,我們可以在實(shí)際的開發(fā)中合理的運(yùn)用這些方法來動(dòng)態(tài)的改變消息的具體表現(xiàn)形式~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容