@property生成setter和getter的声明
,同时生成属性对应的成员变量,而且前面加一个下划线_
。若是将getter和setter的实现同时重写以后,它不会帮助生成属性对应的变量名。 (TestOne.m)这种写法,没法编译经过,由于_name已经不存在。数据库
@interface TestOne : NSObject @property (nonatomic, copy) NSString *name; @end @implementation TestOne - (void)setName:(NSString *)name { _name = name; // Use of undeclared identifier '_name' } - (NSString *)name { return _name; // Use of undeclared identifier '_name' } @end
比较特殊的一种状况是readonly的属性只会帮助生成getter的声明和变量。,若是当getter被重写以后,成员变量也不存在。编程
@interface TestTwo : NSObject @property (readonly, nonatomic, copy) NSString *name; @end @implementation TestTwo - (NSString *)name { return _name; // Use of undeclared identifier '_name' } @end
若是在.h使用@property声明了方法属性,又想在.m重写方法怎么办呢。一般咱们会使用@synthesize.数组
@synthesize是按照系统默认规则帮助生成getter和setter的实现
,同时生成一个紧跟在关键字@synthesize
后面的属性对应的成员变量,还能够更改属性对应的成员变量的名字。
同时若是使用了@synthesize以后,还能够继续重写getter和setter的实现,同时它帮助生成的成员变量依然存在。下面的代码是没有任何问题的。app
@interface TestThree : NSObject @property (nonatomic, copy) NSString *name; @end @implementation TestThree @synthesize name = _name; // 使用 @synthesize name 生成的成员变量是'name' - (void)setName:(NSString *)name { _name = name; } - (NSString *)name { return _name; } @end
这时候@property的做用仅仅是至关于对外声明了方法原型:框架
- (void)setName:(NSString *)name; - (NSString *)name;
可是不能够将.h中的@property (nonatomic, copy) NSString *name;
替换为上面两句方法声明,由于@synthesize使用的前提是使用@property声明过这个属性。编程语言
对于readonly的属性,一样可使用@synthesize关键字找到属性对应的成员变量名,而后重写getter就没有问题了。ide
咱们都知道对应@dynamic修饰的属性,须要手动实现这个属性的getter和setter,不然虽然编译阶段可以经过,可是运行时会形成崩溃,错误信息为没有指定的方法。编译时没问题,运行时才执行相应的方法,这就是所谓的动态绑定。
例以下面的代码函数
@interface TestFour : NSObject @property (nonatomic, copy) NSString *name; @end @implementation TestFour @dynamic name; @end // 在main.m中 TestFour *testFour = [[TestFour alloc] init]; testFour.name = @"Mike"; //已经奔溃 [TestFour setName:]: unrecognized selector sent to instance NSLog(@"%@", testFour.name);
这里就体现出了@synthesize和@dynamic的区别:
@synthesize的语义是若是你没有手动实现setter方法和getter方法,那么编译器会自动为你加上这两个方法的实现。
@dynamic告诉编译器,属性的setter与getter方法须要实现,可是不会自动帮助生成。(固然对于readonly的属性只需提供getter实现便可)测试
@dynamic最经常使用的使用是在NSManagedObject中,此时不须要显示编程setter和getter方法。缘由是:其getter和setter方法会在运行时动态建立,由Core Data框架为此类属性生成存取方法。那么CoreData究竟如何帮助NSManagedObject的子类生成子类属性的getter和setter实现的呢。咱们大致能够模拟一下这个过程:编码
根据OC运行时的特性,当子类的方法没有实现的实现,会去寻找父类的方法实现,为了语义上更好理解我使用Person和它的父类Super用来测试:(这其中Person模拟的是NSManagedObject的子类)
@interface Person : Super @property (nonatomic, copy) NSString *name; @end @implementation Person @dynamic name; @end @interface Super : NSObject @end @implementation Super - (void)setName:(NSString *)name { NSLog(@"执行了Super的setName:"); // setName .... } - (NSString *)name { NSLog(@"执行了Super的name"); return @"Mike在Super中设置"; } @end Person *person = [[Person alloc] init]; person.name = @"Mike"; NSLog(@"%@", person.name);
所以上面的程序执行结果为:
执行了Super的setName: 执行了Super的name Mike在Super中设置
那么问题就来了NSManagedObject是如何截获它子类的全部属性的getter和setter方法的调用,并完成代码实现的,毕竟它不会傻乎乎地把全部的属性都写一个getter和setter方法吧。虽然不知道它的具体实现方法,可是能够模拟一下这个过程。
这里提供一种利用消息转发机制来实现,主要用到两个方法- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
和- (void)forwardInvocation:(NSInvocation *)anInvocation
:
methodSignatureForSelector:
方法用来返回参数指定的SEL的指定的方法签名(方法签名是各类编程语言中通用的概念,主要是对方法的返回值类型、参数类型和参数的个数的描述,函数重载的概念就是同名方法具备不一样的方法签名)。对于- (void)setName:(NSString *)name
方法的方法签名就是:返回值为void类型参数个数为1参数类型为NSString。用天然语言描述是这样,若是用OC的方法编码描述就是:@"v@:@"(为何这样写:可参考我以前关于Runtime的日志).methodSignatureForSelector:
调用的时机为:对象进行任何方法调用都会通过这个方法的过滤。
要注意的是,若是经过经过调用这个方法以后没有获得参数Selector对应的方法签名,那么就会直接致使奔溃,错误为:unrecognized selector sent to xxx
。而若是找到了方法的签名,则会继续调用forwardInvocation:
以求经过消息转发的方式找到方法实现。
forwardInvocation:
方法用来实现消息转发,也就是在它的内部处理一些接收到的方法的实现细节,或者将实现的细节交给其余对象。有关这个方法apple有一段长长的文档,我简单地翻译了一下:
当一个对象发送了一个消息,它却没有相应的实现方法,runtime系统会给这个接受者一个机会来委派这个消息给另外一个接受者。它经过建立一个NSInvocation对象委派这个消息,这个对象表明着这个消息同时会发送给接受者一个包含着这个NSInvocation对象做为参数的forwardInvocation:消息。而后,接收者的forwardInvocation:方法就能够选择转发这个消息给另外一个对象。(若是那个对象也不对这个消息响应,它也会给一个机会转发它)。
所以,forwardInvocation:方法容许一个对象为某些消息创建与对这个对象有影响的其它对象的关系。好比,转发对象能"继承"一些它要转发的对象的特性。
IMPORTANT
想要响应你的对象本身不能识别的方法,除了forwardInvocation:
外,你还必须重写methodSignatureForSelector:
方法.消息转发机制使用从methodSignatureForSelector:
得到的信息来建立被转发的NSInvocation对象。 重写方法时,必须为指定的selector提供一个合适的方法签名,。要么是经过预先格式化的签名,要么就从另外一个对象中获取。
一个
forwardInvocation:
方法的实现包括两项任务:
设置一个可以在anInvocation中响应消息编码的对象,对全部的消息而言,这个对象没必要相同。
用anInvocation对那个对象发送消息。anInvocation会持有结果值,runtime系统会把这个结果值取出并传递给最初的发送者。
这里有一个-methodSignatureForSelector:
和-forwardInvocation:
方法的使用示例
例如在Super类中的实现修改成:
@implementation Super - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { NSString *selName = NSStringFromSelector(aSelector); if ([selName isEqualToString:@"tenAdd:"]) { // 过滤`tenAdd:`方法,指定它的方法签名 return [NSMethodSignature signatureWithObjCTypes:"i@:@"]; // 'i':int, '@:':OC方法,'@':对象类型 } else { return [super methodSignatureForSelector:aSelector]; } } - (void)forwardInvocation:(NSInvocation *)anInvocation { NSString *selName = NSStringFromSelector([anInvocation selector]); if ([selName isEqualToString:@"tenAdd:"]) { // 在这里实现`tenAdd:`方法 NSNumber *arg; [anInvocation getArgument:&arg atIndex:2]; // 0:self 1:_cmd 2:@(3) NSLog(@"%@", NSStringFromClass([[anInvocation target] class])); // Person int result = 10 + [arg intValue]; [anInvocation setReturnValue:&result]; } else { [super forwardInvocation:anInvocation]; } } @end
而后进行以下的测试
Person *person = [[Person alloc] init]; // 在子类(Person类)中调用`tenAdd:`方法 NSLog(@"%zd", [person performSelector:@selector(tenAdd:) withObject:@(3)]); // 13
在上面的Super实现中会接收到来自子类的对于各类方法的调用消息,使用methodSignatureForSelector:(SEL)aSelector
获取一个方法的签名,若是发现方法名是@"tenAdd:"那么它的签名就是"i@:@",在方法forwardInvocation:
中实现了@"tenAdd:"方法的内容用10和传递过来的参数相加,并将计算的结果做为这个方法调用(invocation类型)的返回值。
理清这两个方法的用法,能够来改造Super类来模拟NSManagedObject类的实现了,使用静态的可变数组tableData表明了数据库表中已经存在的数据,columnNames表明全部的列名的集合,对应着每一个Person类的属性名。经过person的id来获取一条记录所在的行,为了方便,在本例中全部的行号都传递0。代码以下:
@implementation Super static NSMutableArray *tableData = nil; static NSArray *columnNames = nil; + (void)initialize { [super initialize]; tableData = [NSMutableArray array]; [tableData addObject:[NSMutableDictionary dictionaryWithDictionary:@{@"name":@"Mike"}]]; [tableData addObject:[NSMutableDictionary dictionaryWithDictionary:@{@"name":@"John"}]]; columnNames = @[@"name"]; } - (NSDictionary *)rowDataInTableWithRowId:(NSInteger)rowId { return tableData[rowId]; } - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { NSString *selName = NSStringFromSelector(aSelector); if ([selName rangeOfString:@"set"].location == 0) { // 处理setter return [NSMethodSignature signatureWithObjCTypes:"v@:@"]; } else if ([columnNames containsObject:selName.description]){ // 处理getter return [NSMethodSignature signatureWithObjCTypes:"@@:"]; } else { return [super methodSignatureForSelector:aSelector]; } } - (void)forwardInvocation:(NSInvocation *)anInvocation { NSString *selName = NSStringFromSelector([anInvocation selector]); if ([selName rangeOfString:@"set"].location == 0) { // setter NSString *propertyName = [selName substringWithRange:NSMakeRange(3, [selName length]- 3)]; propertyName = [propertyName stringByReplacingCharactersInRange:NSMakeRange(0,1) withString:[[propertyName substringToIndex:1] lowercaseString]]; propertyName = [propertyName characterAtIndex:propertyName.length - 1] == ':' ? [propertyName substringToIndex:propertyName.length - 1] : propertyName; NSString *arg; [anInvocation getArgument:&arg atIndex:2]; id obj = [self rowDataInTableWithRowId:0]; // 这里始终传入0,表明着表中的第一条数据。实际上rowId应该从[anInvocation target]中获取, 也就是从Person对象中获取。 [obj setObject:arg forKey:propertyName]; } else if ([columnNames containsObject:selName.description]){ // getter id obj = [self rowDataInTableWithRowId:0]; id value = [obj objectForKey:selName.description]; [anInvocation setReturnValue:&value]; // 设置返回值 } else { [super forwardInvocation:anInvocation]; } } @end
在main.m中进行测试,
Person *person = [[Person alloc] init]; person.name = @"修改后的Mike"; NSLog(@"%@", person.name); // 修改后的Mike
正式如咱们指望的结果。这样咱们没有改变Person类的任何代码,只是修改了它的父类,便将Person类全部属性的getter和setter全都实现了。并且这是一个通用的代码,即便再给Person类增长别的属性,也没有任何问题。
最近看到了AFN中使用@dynamic的代码,可是这些并非咱们经常使用的代码。如在(2.x版本)AFHTTPRequestOperation.m中:
@interface AFURLConnectionOperation () @property (readwrite, nonatomic, strong) NSURLRequest *request; @property (readwrite, nonatomic, strong) NSURLResponse *response; @end @interface AFHTTPRequestOperation () @property (readwrite, nonatomic, strong) NSHTTPURLResponse *response; @property (readwrite, nonatomic, strong) id responseObject; @property (readwrite, nonatomic, strong) NSError *responseSerializationError; @property (readwrite, nonatomic, strong) NSRecursiveLock *lock; @end @implementation AFHTTPRequestOperation @dynamic response; @dynamic lock; // ... // ... @end
AFHTTPRequestOperation原本就是AFURLConnectionOperation的子类,但是在这里居然又为它加了一个拓展,对于response和lock属性原本也是从AFURLConnectionOperation继承而来,但在子类中又使用了扩展从新定义了一下,而在AFURLConnectionOperation.m中他们也有一样的定义:
在.h中
@interface AFURLConnectionOperation
@property (readonly, nonatomic, strong) NSURLResponse *response;
// ...
@end
在AFURLConnectionOperation.m中AFURLConnectionOperation的扩展中
@interface AFURLConnectionOperation ()
@property (readwrite, nonatomic, strong) NSRecursiveLock lock;
@property (readwrite, nonatomic, strong) NSURLResponse response;
// ...
@end
在AFHTTPRequestOperation.h中
@interface AFURLConnectionOperation : AFURLConnectionOperation
@property (readonly, nonatomic, strong) NSHTTPURLResponse *response;
// ...
@end
对此我作了以下分析,(AFURLConnectionOperation简称为URLCO, AFHTTPRequestOperation简称为HTTPRO)
首先为何URLCO中已经定义了response属性,为何子类HTTPRO中还要定义,这个是很好理解的:
URLCO中的reponse是NSURLResponse类型,而HTTPRO中的response是NSHTTPURLResponse类型,对于使用HTTPRO的response属性的状况,省去了诸如类型强转等重复性的操做,面向接口编程的原则中有抽象类的属性使用抽象类
的思想,而这里是具体的类的属性使用具体的类
。
为何要在扩展中从新定义属性response?
实际上这里的response定义是和@dynamic配合使用的:
首先,response已经在URLCO.h中定义为readonly若是要在URLCO.m中为成员变量赋值,或者修改其值,要使用@synthesize生成成员变量的getter和setter同时保证成员变量依然存在。AFN并无使用这种方法,而是经过在扩展中增长readwrite的关键字再次定义这样一样使getter和setter可用,属性也依然存在,而在URLCO.m中不须要@dynamic修饰属性,由于已经能够确保getter和setter的实现。
再者,在子类HTTPRO中,一样使用上面的方法依然使得在HTTPRO.m中response的getter和setter可用了,可是在getter和setter的实现内部与父类URLCO中的reponse属性失去了联系,这是后配合使用@dynamic response使得原本的setter和gette实现变得不可用,可是对于调用依然没有编译期间的错误,而在运行阶段,实际是调用了父类URLCO中的gette和setter,继续将方法调用向上传递。
而对于为何要在HTTPRO中再次定义父类的扩展?
对于request属性来讲是有用的,由于子类中没有定义过request,可是对response来讲是没有什么意义的,我的以为这句能够直接删掉。