参考自:http://www.appcoda.com/understanding-key-value-observing-coding/
在实际开发中,经常有需要属性变更时,需要得到通知。可行的方式有:
- 重写setter方法
- 发通知notification
但上面的方式都比较麻烦
Key-Value Coding(KVC)
KVC是一种间接的访问对象属性的一种机制,使用字符串来标识属性,不是通过调用accessor方法,而是直接通过实例变量来访问它们
比如,我们原来通过如下的方式给某个属性赋值
self.firstname = @"John";
_firstname = @"John";
而使用KVC,可以这样实现
[self setValue:@"John" forKey:@"firstname"];
再举一个例子
[someObject.someProperty setText:@"This is a text"];
使用KVC的形式如下:
[self setValue:@"This is a text" forKeyPath:@"someObject.someProperty.text"];
KVC获取属性的值
NSLog(@"%@", [self valueForKey:@"firstname"];
使用KVC需遵守NSKeyValueCoding协议,如果继承自NSObject,由于NSObject已经实现了这个协议,就不用再实现饿了。一般会用到两个方法:
- setValue:forKey:需要一个key
- setValue:forKeyPath:需要一个keypath
key值的是一个单独的属性,keypath使用的是点语法,表示的是遍历对应的属性知道找到期望的属性
@interface Children : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic) NSUInteger age;
@end
用KVC给它的一个实例赋值:
[self.child1 setValue:@"George" forKeyPath:@"name"];
[self.child1 setValue:[NSNumber numberWithInteger:15] forKey:@"age"];
NSString *childName = [self.child1 valueForKey:@"name"];
NSUInteger childAge = [[self.child1 valueForKey:@"age"] integerValue];
修改Children类,增加一个属性:
@interface Children : NSObject
...
@property (nonatomic, strong) Children *child;
@end
给child.child赋值,使用KVC
self.child2 = [[Children alloc] init];
[self.child2 setValue:@"Mary" forKeyPath:@"name"];
[self.child2 setValue:[NSNumber numberWithInteger:35] forKeyPath:@"age"];
self.child2.child = [[Children alloc] init];
[self.child2 setValue:@"Andrew" forKeyPath:@"child.name"];
[self.child2 setValue:[NSNumber numberWithInteger:5] forKeyPath:@"child.age"];
Key-Value Observing
KVO有以下的标准
1.你要观察属性的类必须符合KVO标准
- 类要符合KVC标准
- 类能手动或自动的发出通知
2.要设置观察者
3.观察者实现对应的方法
一般用来观察属性值的变化,使用步骤如下:
- 添加观察者:observeValueForKeyPath:ofObject:change:context:
- 在观察者中实现此方法:observeValueForKeyPath:ofObject:change:context:
- 移除观察者:removeObserver: forKeyPath:
[self.child1 addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:child1Context];
[self.child1 addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:child1Context];
[self.child1 setValue:@"Michael" forKeyPath:@"name"];
[self.child1 setValue:[NSNumber numberWithInteger:20] forKeyPath:@"age"];
[self.child2 addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:child2Context];
[self.child2 setValue:[NSNumber numberWithInteger:45] forKeyPath:@"age"];
child1Context 和 child2Context context 为属性的唯一标识符,也可以为nil或者NULL,它们的定义如下:
static void *child1Context = &child1Context;
static void *child2Context = &child2Context;
observeValueForKeyPath:ofObject:change:context:如下:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if (context == child1Context) {
if ([keyPath isEqualToString:@"name"]) {
NSLog(@"The name of the first child was changed");
NSLog(@"%@", change);
}
if ([keyPath isEqualToString:@"age"]) {
NSLog(@"The age of the first child was changed");
NSLog(@"%@", change);
}
}else if(context == child2Context) {
if ([keyPath isEqualToString:@"age"]) {
NSLog(@"The age of the second child was changed");
NSLog(@"%@", change);
}
}else{
if ([keyPath isEqualToString:@"siblings"]) {
NSLog(@"%@", change);
}
}
}
查看输出结果如下:
2014-08-30 15:33:15.511 KVCODemo[2419:60b] The name of the first child was changed
2014-08-30 15:33:15.512 KVCODemo[2419:60b] {
kind = 1;
new = Michael;
old = George;
}
2014-08-30 15:33:15.512 KVCODemo[2419:60b] The age of the first child was changed
2014-08-30 15:33:15.513 KVCODemo[2419:60b] {
kind = 1;
new = 20;
old = 15;
}
2014-08-30 15:33:15.514 KVCODemo[2419:60b] The age of the second child was changed
2014-08-30 15:33:15.514 KVCODemo[2419:60b] {
kind = 1;
new = 45;
old = 35;
}
在viewWillDissapear:中移除
-(void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
[self.child1 removeObserver:self forKeyPath:@"name"];
[self.child1 removeObserver:self forKeyPath:@"age"];
[self.child2 removeObserver:self forKeyPath:@"age"];
}
现在,假设我们不想在name改变时通知name的属性值已改变,该怎么做呢?可以实现automaticallyNotifiesObserverForKey:类方法,如下:
在Children类的.m文件中,实现此方法:
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key
{
if ([key isEqualToString:@"name"]) {
return NO;
}else{
return [super automaticallyNotifiesObserversForKey:key];
}
}
这时查看输出结果,可以发现name属性改变通知被屏蔽:
2014-08-30 17:28:01.266 KVCODemo[2715:60b] The age of the first child was changed
2014-08-30 17:28:01.267 KVCODemo[2715:60b] {
kind = 1;
new = 20;
old = 15;
}
2014-08-30 17:28:01.267 KVCODemo[2715:60b] The age of the second child was changed
2014-08-30 17:28:01.268 KVCODemo[2715:60b] {
kind = 1;
new = 45;
old = 35;
}
此时,如果还想继续name属性改变的通知,可以使用手动通知:
[self.child1 willChangeValueForKey:@"name"];
[self.child1 setValue:@"Michael" forKeyPath:@"name"];
[self.child1 didChangeValueForKey:@"name"];
查看输出结果:
2014-08-30 17:31:09.877 KVCODemo[2745:60b] The name of the first child was changed
2014-08-30 17:31:09.877 KVCODemo[2745:60b] {
kind = 1;
new = Michael;
old = George;
}
2014-08-30 17:31:09.878 KVCODemo[2745:60b] The age of the first child was changed
2014-08-30 17:31:09.878 KVCODemo[2745:60b] {
kind = 1;
new = 20;
old = 15;
}
2014-08-30 17:31:09.879 KVCODemo[2745:60b] The age of the second child was changed
2014-08-30 17:31:09.879 KVCODemo[2745:60b] {
kind = 1;
new = 45;
old = 35;
}
其实通知是在didChangeValueForKey: 方法之后发出的,所以如果你不想立即发出Notification,可以在别的地方调用didChangeValueForKey: 方法
也可以在setter方法中触发,如下,可达到同样的效果:
-(void)setName:(NSString *)name{
[self willChangeValueForKey:@"name"];
_name = name;
[self didChangeValueForKey:@"name"];
}
观察数组的变化
数组array不是KVC兼容的,所以处理它们与处理单个属性并不一样。
更改Children类,如下:
@interface Children : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic) NSUInteger age;
@property (nonatomic, strong) Children *child;
@property (nonatomic, strong) NSMutableArray *siblings;
在Children.h中添加如下的方法,注意siblings与方法名中的Sibling要一致,区别只是大小写:
- (NSInteger)countOfSiblings;
- (id)objectInSiblingsAtIndex:(NSUInteger)index;
- (void)insertObject:(NSString *)object inSiblingsAtIndex:(NSUInteger)index;
- (void)removeObjectFromSiblingsAtIndex:(NSUInteger)index;
具体如下:
- (NSInteger)countOfSiblings
{
return self.siblings.count;
}
- (id)objectInSiblingsAtIndex:(NSUInteger)index
{
return [self.siblings objectAtIndex:index];
}
- (void)insertObject:(NSString *)object inSiblingsAtIndex:(NSUInteger)index
{
[self.siblings insertObject:object atIndex:index];
}
- (void)removeObjectFromSiblingsAtIndex:(NSUInteger)index
{
[self.siblings removeObjectAtIndex:index];
}
对数组进行操作
[self.child1 addObserver:self forKeyPath:@"siblings" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
[self.child1 insertObject:@"Alex" inSiblingsAtIndex:0];
[self.child1 insertObject:@"Bob" inSiblingsAtIndex:1];
[self.child1 insertObject:@"Mary" inSiblingsAtIndex:2];
[self.child1 removeObjectFromSiblingsAtIndex:1];
控制台输出结果如下:
014-08-30 17:31:09.880 KVCODemo[2745:60b] {
indexes = "<NSIndexSet: 0x8c90e90>[number of indexes: 1 (in 1 ranges), indexes: (0)]";
kind = 2;
new = (
Alex
);
}
2014-08-30 17:31:09.881 KVCODemo[2745:60b] {
indexes = "<NSIndexSet: 0x8d7c650>[number of indexes: 1 (in 1 ranges), indexes: (1)]";
kind = 2;
new = (
Bob
);
}
2014-08-30 17:31:09.881 KVCODemo[2745:60b] {
indexes = "<NSIndexSet: 0x8d7c650>[number of indexes: 1 (in 1 ranges), indexes: (2)]";
kind = 2;
new = (
Mary
);
}
2014-08-30 17:31:09.882 KVCODemo[2745:60b] {
indexes = "<NSIndexSet: 0x8f729b0>[number of indexes: 1 (in 1 ranges), indexes: (1)]";
kind = 3;
old = (
Bob
);
}
但是这样写有一个毛病,就是针对每一个数组,都要重复的写如上的方法。现在可以采用这样的方法,重新写一个类:
@interface KVCMutableArray : NSObject
@property (nonatomic, strong) NSMutableArray *array;
-(NSUInteger)countOfArray;
- (id)objectInArrayAtIndex:(NSUInteger)index;
- (void)insertObject:(id)object inArrayAtIndex:(NSUInteger)index;
- (void)removeObjectFromArrayAtIndex:(NSUInteger)index;
- (void)replaceObjectInArrayAtIndex:(NSUInteger)index withObject:(id)object;
@end
@implementation KVCMutableArray
- (instancetype)init
{
self = [super init];
if (self) {
self.array = [[NSMutableArray array] init];
}
return self;
}
- (NSUInteger)countOfArray
{
return self.array.count;
}
- (id)objectInArrayAtIndex:(NSUInteger)index
{
return [self.array objectAtIndex:index];
}
- (void)insertObject:(id)object inArrayAtIndex:(NSUInteger)index
{
[self.array insertObject:object atIndex:index];
}
- (void)removeObjectFromArrayAtIndex:(NSUInteger)index
{
[self.array removeObjectAtIndex:index];
}
- (void)replaceObjectInArrayAtIndex:(NSUInteger)index withObject:(id)object
{
[self.array replaceObjectAtIndex:index withObject:object];
}
@end
使用时如下:
@interface Children : NSObject
...
@property (nonatomic, strong) KVCMutableArray *cousins;
@end
[self.child1 addObserver:self forKeyPath:@"cousins.array" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
...
else{
...
if ([keyPath isEqualToString:@"cousins.array"]) {
NSLog(@"%@", change);
}
}
}