#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框架為此類屬性生成存取方法。