Objective-C中的@dynamic
轉自:http://blog.csdn.net/haishu_zheng/article/details/12873151 haishu_zheng的專欄
一、@dynamic與@synthesize的區別
@property有兩個對應的詞,一個是@synthesize,一個是@dynamic。如果@synthesize和@dynamic都沒寫,那麼默認的就是@syntheszie var = _var;
@synthesize的語義是如果你沒有手動實現setter方法和getter方法,那麼編譯器會自動爲你加上這兩個方法。
@dynamic告訴編譯器,屬性的setter與getter方法由用戶自己實現,不自動生成。(當然對於readonly的屬性只需提供getter即可)。假如一個屬性被聲明爲@dynamic var,然後你沒有提供@setter方法和@getter方法,編譯的時候沒問題,但是當程序運行到instance.var =someVar,由於缺setter方法會導致程序崩潰;或者當運行到 someVar = var時,由於缺getter方法同樣會導致崩潰。編譯時沒問題,運行時才執行相應的方法,這就是所謂的動態綁定。
二、通過私有變量來實現@dynamic的存取方法
1)Book.h
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
@interface Book :NSObject
{
@private
__strong NSString *_name;
__strong NSString *_author;
}
@property(nonatomic, copy) NSString *name;
@property(nonatomic, copy) NSString *author;
@property(nonatomic, copy) NSString*version;
@end
2)Book.m
#import "Book.h"
@implementation Book
@dynamic name;
@dynamicauthor;
@synthesizeversion = _version;
- (id)init
{
self = [super init];
if(self)
{
}
return self;
}
- (NSString *)name
{
if(nil == _name)
{
_name = @"you forgot inputbook name";
}
return _name;
}
- (void)setName:(NSString *)name
{
_name = name;
NSLog(@"_name address:%p", _name);
}
- (NSString *)author
{
if(nil == _author)
{
_author = @"you forgot inputbook author";
}
return _author;
}
- (void)setAuthor:(NSString *)author
{
_author = author;
}
@end
從上面的代碼可以看出,用@dynamic後,可以在存取方法中訪問一個私有變量來賦值或取值。而@synthesize則直接用@synthesize var = _var;來讓屬性和私有變量直接等同起來。這就是二者在書寫形式上的差別。
三、通過消息轉發來實現@dynamic的存取方法
若對於一個屬性使用了@dynamic var = _var,則編譯器立馬報錯。這樣你就無法像@synthesize那樣在var的setter方法和getter方法中使用_var,當然你更不能編寫如下的setter方法和getter方法
- (void)setVar:(id)newVar
{
self.var =newVar;
}
- (void)var
{
return self.var;
}
這兩個方法都是自己調用自己,會導致無限循環直接導致程序崩潰。
這裏提供一種利用消息轉發機制來實現@dynamic的setter和getter方法。
先上代碼:
1)Book.h
#import <Foundation/Foundation.h>
@interface Book :NSObject
{
@private
NSMutableDictionary *_propertiesDict;
}
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString*author;
@property (nonatomic, copy) NSString*version;
@end
2)Book.m
#import "Book.h"
@implementation Book
@dynamic name; // 不能寫成name = _name;否則編譯器馬上報錯
@dynamic author;
@synthesizeversion;
- (id)init
{
self = [super init];
if(self)
{
_propertiesDict = [[NSMutableDictionary alloc] init];
}
return self;
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector
{
NSString *sel = NSStringFromSelector(selector);
if ([sel rangeOfString:@"set"].location == 0)
{
return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
}
else
{
return [NSMethodSignature signatureWithObjCTypes:"@@:"];
}
}
- (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:&objatIndex:2];
[_propertiesDict setObject:obj forKey:key];
}
else
{
NSString *obj = [_propertiesDict objectForKey:key];
[invocation setReturnValue:&obj];
}
}
@end
3)main.m
#import <Foundation/Foundation.h>
#import "Book.h"
int main(int argc, const char * argv[])
{
@autoreleasepool
{
Book *book = [[Book alloc] init];
book.name = @"c++ primer";
book.author = @"Stanley B.Lippman";
book.version = @"5.0";
NSLog(@"%@", book.name);
NSLog(@"%@", book.author);
NSLog(@"%@", book.version);
}
return 0;
}
程序分析:
1)在給程序添加消息轉發功能以前,必須覆蓋兩個方法,即methodSignatureForSelector:和forwardInvocation:。methodSignatureForSelector:的作用在於爲另一個類實現的消息創建一個有效的方法籤名。forwardInvocation:將選擇器轉發給一個真正實現了該消息的對象。
2)Objective-C中的方法默認被隱藏了兩個參數:self和_cmd。self指向對象本身,_cmd指向方法本身。舉兩個例子來說明:
例一:- (NSString *)name
這個方法實際上有兩個參數:self和_cmd。
例二:- (void)setValue:(int)val
這個方法實際上有三個參數:self, _cmd和val。
被指定爲動態實現的方法的參數類型有如下的要求:
A.第一個參數類型必須是id(就是self的類型)
B.第二個參數類型必須是SEL(就是_cmd的類型)
C.從第三個參數起,可以按照原方法的參數類型定義。舉兩個例子來說明:
例一:setHeight:(CGFloat)height中的參數height是浮點型的,所以第三個參數類型就是f。
例二:再比如setName:(NSString *)name中的參數name是字符串類型的,所以第三個參數類型就是@
3)在main.m中有一句代碼是book.name = @"c++ primer";程序運行到這裏時,會去Book.m中尋找setName:這個賦值方法。但是Book.m裏並沒有這個方法,於是程序進入methodSignatureForSelector:中進行消息轉發。執行完之後,以"v@:@"作爲方法簽名類型返回。
這裏v@:@是什麼東西呢?實際上,這裏的第一個字符v代表函數的返回類型是void,後面三個字符參考上面2)中的解釋就可以知道,分別是self, _cmd, name這三個參數的類型id, SEL, NSString。
接着程序進入forwardInvocation方法。得到的key爲方法名稱setName:,然後利用[invocationgetArgument:&objatIndex:2];獲取到參數值,這裏是“c++ primer”。這裏的index爲什麼要取2呢?如前面分析,第0個參數是self,第1個參數是_cmd,第2個參數纔是方法後面帶的那個參數。
最後利用一個可變字典來賦值。這樣就完成了整個setter過程。
4)在main.m中有一句代碼是 NSLog(@"%@", book.name);,程序運行到這裏時,會去Book.m中尋找name這個取值方法 。但是Book.m裏並沒有這個取值方法,於是程序進入methodSignatureForSelector:中進行消息轉發。執行完之後,以"@@:"作爲方法簽名類型返回。這裏第一字符@代表函數返回類型NSString,第二個字符@代表self的類型id,第三個字符:代表_cmd的類型SEL。
接着程序進入forwardInvocation方法。得到的key爲方法名稱name。最後根據這個key從字典裏獲取相應的值,這樣就完成了整個getter過程。
5)注意,調試代碼的過程,我們發現只有name和author的賦值和取值進入methodSignatureForSelector:和forwardInvocation:這兩個方法。還有一個屬性version的賦值和取值,並沒有進入methodSignatureForSelector:和forwardInvocation:這兩個方法。這是因爲,version屬性被標識爲@synthesize,編譯器自動會加上setVersion和version兩個方法,所以就不用消息轉發了。
四、@dynamic在NSManagedObject的子類中的使用
@dynamic最常用的使用是在NSManagedObject中,此時不需要顯示編程setter和getter方法。原因是:@dynamic告訴編譯器不做處理,使編譯通過,其getter和setter方法會在運行時動態創建,由Core Data框架爲此類屬性生成存取方法。