消息轉(zhuǎn)發(fā)機(jī)制(動態(tài)消息轉(zhuǎn)發(fā))

#import <Foundation/Foundation.h>
@interface Book : NSObject

@property (nonatomic,copy) NSString *name;
@property (nonatomic,copy) NSString *price;
@property (nonatomic,copy) NSString *author;

@end
#import "Book.h"

@interface Book ()
//真正的消息實(shí)現(xiàn)對象
@property (nonatomic,strong) NSMutableDictionary *bookProDic;

@end

@implementation Book

@dynamic name,price;
@synthesize author;

- (instancetype)init
{
    self = [super init];
    if (self) {
        _bookProDic = [[NSMutableDictionary alloc]init];
    }
    return self;
}

//為另一個類實(shí)現(xiàn)的消息創(chuàng)建一個有效的方法簽名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector{
    NSString *sel = NSStringFromSelector(selector);
    if ([sel rangeOfString:@"set"].location == 0){
        return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
    }else{
        return [NSMethodSignature signatureWithObjCTypes:"@@:"];
    }
}

//將選擇器轉(zhuǎn)發(fā)給一個真正實(shí)現(xiàn)了該消息的對象,就是_bookProDic
- (void)forwardInvocation:(NSInvocation *)invocation{
    
    NSString *key = NSStringFromSelector([invocation selector]);
    if ([key rangeOfString:@"set"].location == 0){
        key= [[key substringWithRange:NSMakeRange(3, [key length]-4)] lowercaseString];
        NSString *obj;
        [invocation getArgument:&obj atIndex:2];
        
        //_bookProDic為實(shí)現(xiàn)set消息的真正對象
        [_bookProDic setValue:obj forKey:key];
    }else{
        //_bookProDic為實(shí)現(xiàn)get消息的真正對象
        NSString *obj = [_bookProDic objectForKey:key];
        [invocation setReturnValue:&obj];
    }
}
@interface ViewController ()

@end

@implementation ViewController


- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    Book *book = [[Book alloc]init];
    book.name = @"Harry Potter";
    book.price = @"¥23";
    book.author = @"JK LuoLin";
    
    NSLog(@"%@",book.name);
    NSLog(@"%@",book.price);
    NSLog(@"%@",book.author);
}

例子分析

1)在給程序添加消息轉(zhuǎn)發(fā)功能以前,必須覆蓋兩個方法,即methodSignatureForSelector:和forwardInvocation:。methodSignatureForSelector:的作用在于為另一個類實(shí)現(xiàn)的消息創(chuàng)建一個有效的方法簽名。forwardInvocation:將選擇器轉(zhuǎn)發(fā)給一個真正實(shí)現(xiàn)了該消息的對象。
2)Objective-C中的方法默認(rèn)被隱藏了兩個參數(shù):self和_cmd。self指向?qū)ο蟊旧?,_cmd指向方法本身。舉兩個例子來說明:
例一:- (NSString *)name
這個方法實(shí)際上有兩個參數(shù):self和_cmd。

例二:- (void)setValue:(int)val

這個方法實(shí)際上有三個參數(shù):self, _cmd和val。

被指定為動態(tài)實(shí)現(xiàn)的方法的參數(shù)類型有如下的要求:

A.第一個參數(shù)類型必須是id(就是self的類型)

B.第二個參數(shù)類型必須是SEL(就是_cmd的類型)

C.從第三個參數(shù)起,可以按照原方法的參數(shù)類型定義。舉兩個例子來說明:

例一:setHeight:(CGFloat)height中的參數(shù)height是浮點(diǎn)型的,所以第三個參數(shù)類型就是f。

例二:再比如setName:(NSString *)name中的參數(shù)name是字符串類型的,所以第三個參數(shù)類型就是@

3)在ViewController.m中有一句代碼是book.name = @"c++ primer";程序運(yùn)行到這里時,會去Book.m中尋找setName:這個賦值方法。但是Book.m里并沒有這個方法,于是程序進(jìn)入methodSignatureForSelector:中進(jìn)行消息轉(zhuǎn)發(fā)。執(zhí)行完之后,以"v@:@"作為方法簽名類型返回。

這里v@:@是什么東西呢?實(shí)際上,這里的第一個字符v代表函數(shù)的返回類型是void,后面三個字符參考上面2)中的解釋就可以知道,分別是self, _cmd, name這三個參數(shù)的類型id, SEL, NSString。

4)在ViewController.m中有一句代碼是 NSLog(@"%@", book.name);,程序運(yùn)行到這里時,會去Book.m中尋找name這個取值方法 。但是Book.m里并沒有這個取值方法,于是程序進(jìn)入methodSignatureForSelector:中進(jìn)行消息轉(zhuǎn)發(fā)。執(zhí)行完之后,以"@@:"作為方法簽名類型返回。這里第一字符@代表函數(shù)返回類型NSString,第二個字符@代表self的類型id,第三個字符:代表_cmd的類型SEL。

接著程序進(jìn)入forwardInvocation方法。得到的key為方法名稱name。最后根據(jù)這個key從字典里獲取相應(yīng)的值,這樣就完成了整個getter過程。
5)注意,調(diào)試代碼的過程,我們發(fā)現(xiàn)只有name和author的賦值和取值進(jìn)入methodSignatureForSelector:和forwardInvocation:這兩個方法。還有一個屬性author的賦值和取值,并沒有進(jìn)入methodSignatureForSelector:和forwardInvocation:這兩個方法。這是因?yàn)?,version屬性被標(biāo)識為@synthesize,編譯器自動會加上setAuthor和author兩個方法,所以就不用消息轉(zhuǎn)發(fā)了。

@dynamic與@synthesize的區(qū)別

@property有兩個對應(yīng)的詞,一個是@synthesize,一個是@dynamic。如果@synthesize和@dynamic都沒寫,那么默認(rèn)的就是@syntheszie var = _var;
@synthesize的語義是如果你沒有手動實(shí)現(xiàn)setter方法和getter方法,那么編譯器會自動為你加上這兩個方法。
@dynamic告訴編譯器,屬性的setter與getter方法由用戶自己實(shí)現(xiàn),不自動生成。(當(dāng)然對于readonly的屬性只需提供getter即可)。假如一個屬性被聲明為@dynamic var,然后你沒有提供@setter方法和@getter方法,編譯的時候沒問題,但是當(dāng)程序運(yùn)行到instance.var =someVar,由于缺setter方法會導(dǎo)致程序崩潰;或者當(dāng)運(yùn)行到 someVar = var時,由于缺getter方法同樣會導(dǎo)致崩潰。編譯時沒問題,運(yùn)行時才執(zhí)行相應(yīng)的方法,這就是所謂的動態(tài)綁定。

@dynamic在NSManagedObject的子類中的使用

@dynamic最常用的使用是在NSManagedObject中,此時不需要顯示編程setter和getter方法。原因是:@dynamic告訴編譯器不做處理,使編譯通過,其getter和setter方法會在運(yùn)行時動態(tài)創(chuàng)建,由Core Data框架為此類屬性生成存取方法。

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

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

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