消息轉(zhuǎn)發(fā)機制

消息轉(zhuǎn)發(fā)機制

在對象上調(diào)用方法,在Objective-C中叫做“傳遞消息”。消息有“名稱”或者“選擇子”,可以接受參數(shù),而且可能還有返回值。
但是由于Objective-C的“動態(tài)綁定機制”,在編譯期向類或者對象發(fā)送了其無法解讀的消息并不會報錯,以為在運行期可以繼續(xù)向類或?qū)ο笾刑砑臃椒?,所以編譯器在編譯時還無法確知類中到底會不會有某種方法實現(xiàn)。當對象接收到無法解讀的消息后,就會啟動“消息轉(zhuǎn)發(fā)”機制。

消息轉(zhuǎn)發(fā)分為兩大階段。
第一階段先征詢接收者,所屬的類,看其是否能動態(tài)添加方法,以處理當前這個“未知選擇子”,成為“動態(tài)方法解析”。
第二階段涉及“完整消息轉(zhuǎn)發(fā)”。如果第一階段執(zhí)行完,還是沒有找到相應(yīng)的選擇子對應(yīng)的方法,此時運行期系統(tǒng)會請求接收者以其他手段來處理與消息有關(guān)的方法調(diào)用。這里又分為兩小步:
首先,請接收者看看有沒有其他對象能夠處理這條消息。若有,則運行期系統(tǒng)會把消息轉(zhuǎn)給那個對象,這個過程稱為“備援接收者”。
其次,如果沒有,則進入第二小步“完整的消息轉(zhuǎn)發(fā)”,運行期系統(tǒng)會把與消息有關(guān)的全部細節(jié)都封裝到NSInvocation對象中,再給接收者最后一次機會,令其設(shè)法解決當前還未處理的這條消息。

動態(tài)方法解析

在這個階段,對象在接收到無法解讀的消息后,首先將調(diào)用其所屬類的下列類方法:

+ (BOOL)resolveInstanceMethod:(SEL)sel
    或
+ (BOOL)resolveClassMethod:(SEL)sel  
//如果尚未實現(xiàn)的方法是實例方法則使用resolveInstanceMethod這個方法,  
//如果未實現(xiàn)的方法是類方法則調(diào)用resolveClassMethod這個方法。  

該方法的參數(shù)就是那個選擇子,其返回值未Boolean類型, 表示這個類是否能新增一個實例方法或者類方法用以處理此選擇子。
下面代碼演示如何使用resolveInstanceMethod來實現(xiàn)動態(tài)方法解析。創(chuàng)建一個Message類,該類聲明了一個- (void)showLog方法,但是在實現(xiàn)文件中,并未實現(xiàn)。而是動態(tài)增加了一個void pingtLog方法。

//Message.h
#import <Foundation/Foundation.h>

@interface Message : NSObject

- (void)showLog;
@end

//Message.m
#import "Message.h"
#import "objc/runtime.h"
@implementation Message

void pingtLog(id self,SEL _cmd);

+ (BOOL)resolveInstanceMethod:(SEL)sel {
       NSString *selectorString = NSStringFromSelector(sel);
       if ([selectorString isEqualToString:@"lowercase"]) {
           class_addMethod(self, sel, (IMP)pingtLog, "@@:");
           return YES;
       }
       return [super resolveInstanceMethod:sel];
}

void pingtLog(id self,SEL _cmd) {
    NSLog(@"ljt");
}  

//在其他地方這樣調(diào)用:
self.message = [[Message alloc] init];
[self.message showLog];
備援接收者

當動態(tài)方法解析發(fā)現(xiàn)還是沒有找到相應(yīng)的方法來響應(yīng)選擇子,則進入第二階段第一小步,“備援接收者”,這一步,運行期系統(tǒng)會請求接收者以其他手段來處理與消息有關(guān)的方法調(diào)用,涉及到的方法為:

- (id)forwardingTargetForSelector:(SEL)aSelector  

方法參數(shù)代表未知的選擇子,若當前對象能夠找到備援對象,則將其返回,若找不到,則返回nil.
下面用代碼演示一下備援接收者過程:
還是使用上一階段的Message對象,但是Message對象沒有實現(xiàn)第一階段。但是有另外一個類Car可以響應(yīng)這個選擇子。

//Message.h
#import <Foundation/Foundation.h>

@interface Message : NSObject

- (void)showLog;
@end

//Message.m
#import "Message.h"
#import "objc/runtime.h"
#import "Car.h"
@implementation Message

// void pingtLog(id self,SEL _cmd);

// + (BOOL)resolveInstanceMethod:(SEL)sel {
//    NSString *selectorString = NSStringFromSelector(sel);
//    if ([selectorString isEqualToString:@"lowercase"]) {
//        class_addMethod(self, sel, (IMP)pingtLog, "@@:");
//        return YES;
//    }
//    return [super resolveInstanceMethod:sel];
// }

- (id)forwardingTargetForSelector:(SEL)aSelector {
   return [[Car alloc] init];
}

// void pingtLog(id self,SEL _cmd) {
//     NSLog(@"ljt");
// }

@end


//Car.h
#import <Foundation/Foundation.h>

@interface Car : NSObject

- (void)showLog;
@end  

//Car.m
#import "Car.h"

@implementation Car

- (void)showLog {
    NSLog(@"car-showLog");
}
@end

//在其他地方這樣調(diào)用:
self.message = [[Message alloc] init];
[self.message showLog];
完整消息轉(zhuǎn)發(fā)

如果經(jīng)歷了前面過程還是無法找到相應(yīng)的方法實現(xiàn),來到這一步的話,那么就啟用完整的消息轉(zhuǎn)發(fā)機制。首先創(chuàng)建NSInvocation對象,把與尚未處理的那條消息有關(guān)的全部細節(jié)都封裝于其中。此對象包含選擇子、目標及參數(shù)。在觸發(fā)NSInvocation對象時,“消息派發(fā)系統(tǒng)”將親自出馬,把消息指派給目標對象。此過程涉及到來個方法:

//對需要轉(zhuǎn)發(fā)的消息進行簽名封裝。
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;  

//改變調(diào)用目標,使消息在新目標上得以調(diào)用
- (void)forwardInvocation:(NSInvocation *)anInvocation;

下面代碼演示完整消息轉(zhuǎn)發(fā)過程。承接上文,Message類并未實現(xiàn)前面兩個過程,將消息轉(zhuǎn)發(fā)給Car類實現(xiàn):

//Message.h
#import <Foundation/Foundation.h>

@interface Message : NSObject

- (void)showLog;
@end

//Message.m
#import "Message.h"
#import "objc/runtime.h"
#import "Car.h"
@implementation Message

// void pingtLog(id self,SEL _cmd);

// + (BOOL)resolveInstanceMethod:(SEL)sel {
//    NSString *selectorString = NSStringFromSelector(sel);
//    if ([selectorString isEqualToString:@"lowercase"]) {
//        class_addMethod(self, sel, (IMP)pingtLog, "@@:");
//        return YES;
//    }
//    return [super resolveInstanceMethod:sel];
// }

//- (id)forwardingTargetForSelector:(SEL)aSelector {
//   return [[Car alloc] init];
//}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    NSString *selectorString = NSStringFromSelector(aSelector);
    if ([selectorString isEqualToString:@"lowercase:"]) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
    }    
    return [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
        SEL selector = [anInvocation selector];
    Car *car = [[Car alloc] init];
    if ([car respondsToSelector:selector]) {
        [anInvocation invokeWithTarget:car];
    }
}

// void pingtLog(id self,SEL _cmd) {
//     NSLog(@"ljt");
// }

@end


//Car.h
#import <Foundation/Foundation.h>

@interface Car : NSObject

- (void)showLog;
@end  

//Car.m
#import "Car.h"

@implementation Car

- (void)showLog {
    NSLog(@"car-showLog");
}
@end

//在其他地方這樣調(diào)用:
self.message = [[Message alloc] init];
[self.message showLog];  

至此,整個的消息轉(zhuǎn)發(fā)流程就完結(jié)了。總結(jié)一下:

若對象無法響應(yīng)某個選擇子,則進入消息轉(zhuǎn)發(fā)流程。
通過運行期的動態(tài)方法解析功能,我們可以在需要用到某個方法事再將其加入類中
對象可以把其無法解讀的某些選擇子轉(zhuǎn)交給其他對象來處理
經(jīng)過上述兩步之后,如果還是無法處理選擇子,那就啟動完整的消息轉(zhuǎn)發(fā)機制

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

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

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