KVC(key - value - coding)键值编码
- 它提供了一种使用字符串而不是通过访问方法访问对象属性的一种机制。
- 通过
setValue: forKey:
的方式对类属性进行赋值操作,key作为属性名,value为属性对应的值。 - 通过
valueForKey:
访问对象的属性
可以通过以下简单的代码来尝试一下。
定义两个类,一个为Person
#import <Foundation/Foundation.h>
#import "Dog.h"
@interface Person : NSObject {
NSString *_sex;
Dog *_dog;
}
@property (nonatomic, copy) NSString *name;
//用来测试KVO对数组的效果
@property (nonatomic, strong) NSMutableArray *dogs;
- (void)showSex;
- (void)showAge;
- (void)showHeight;
@end
#import "Person.h"
@interface Person () {
NSString *age;
}
@property (nonatomic, copy) NSString *height;
@end
@implementation Person
- (instancetype)init {
if (self = [super init]) {
_dog = [[Dog alloc]init];
_dogs = [NSMutableArray new];
}
return self;
}
- (void)showAge {
NSLog(@"age : %@",age);
}
- (void)showSex {
NSLog(@"sex : %@",_sex);
}
- (void)showHeight {
NSLog(@"height : %@",_height);
}
一个为Dog类
#import <Foundation/Foundation.h>
@interface Dog : NSObject
@property (nonatomic, copy) NSString *name;
@end
#import "Dog.h"
@interface Dog () {
NSString *_age;
NSMutableDictionary *_undefinedKeyDict;
}
@property (nonatomic, copy) NSString *weight;
@end
@implementation Dog
- (void)setWeight:(NSString *)weight {
if (![_weight isEqualToString:weight]) {
_weight = [weight copy];
}
}
//不做实现的话会崩溃
- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
if (!_undefinedKeyDict) {
_undefinedKeyDict = [NSMutableDictionary dictionary];
}
[_undefinedKeyDict setObject:value forKey:key];
}
- (id)valueForUndefinedKey:(NSString *)key {
return [_undefinedKeyDict objectForKey:key];
}
@end
Person *p = [[Person alloc]init];
p.name = @"vayne";
NSLog(@"name : %@",p.name);
[p setValue:@"famale" forKey:@"sex"];
[p showSex];
[p setValue:@"18" forKey:@"age"];
[p showAge];
[p setValue:@"185" forKey:@"height"];
[p showHeight];
Dog *d = [[Dog alloc]init];
d.name = @"huahua";
NSLog(@"dog name : %@",d.name);
[d setValue:@"duoduo" forKey:@"name"];
NSLog(@"dog name : %@",d.name);
[d setValue:@"30" forKey:@"weight"];
NSLog(@"weight : %@",[d valueForKey:@"weight"]);
[d setValue:@"3" forKey:@"age"];
NSLog(@"age : %@",[d valueForKey:@"age"]);
[p setValue:@"dog" forKeyPath:@"dog.name"];
NSLog(@"dog name : %@",[p valueForKeyPath:@"dog.name"]);
//keyPath dog.xxxx并不存在,如果在dog类中不实现setValue: forUndefinedKey 会崩溃。
[p setValue:@"xxxx" forKeyPath:@"dog.xxxx"];
NSLog(@"xxxx : %@",[p valueForKeyPath:@"dog.xxxx"]);
[d setValue:@"yyyy" forKey:@"yyyy"];
NSLog(@"yyyy : %@",[d valueForKey:@"yyyy"]);
输出结果
通过上边的简单代码可以对KVC有个简单的了解,正因为这个特性,我们可以通过KVC来调用一些私有属性。正因为这样,KVO就是基于KVC实现的
KVO(key - value - observed)键值观察
观察者模式(有时又被成为订阅模式/发布模式)是软件设计模式的一种。在此模式中,一个目标对象管理所有相依赖于它的观察者对象,并且在它本身的状态改变时主动发出通知。这通常透过呼叫观察者所提供的方法来实现。
通过- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context;
添加观察机制。其中observer
观察方法的持有者,keyPath
属性的名字,options
观察键值变化的几种可选方式,context
可以传NULL
;
接上边的代码,看一下KVO
[p addObserver:self forKeyPath:@"sex" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
[p setValue:@"male" forKey:@"sex"];
/*
如果被观察对象销毁,要移除其观察事件,否则会有崩溃。
*/
[p removeObserver:self forKeyPath:@"sex"];
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
NSLog(@"%@ %@ %@ %@ ",keyPath,[object class],change,context);
}
输出结果
当KVO作用于NSMutableArray时,观察并不会被调用,需要手动调用方法
//添加观察事件
[p addObserver:self forKeyPath:@"dogs" options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:nil];
//需要手动调用
[p willChangeValueForKey:@"dogs"];
[p.dogs addObject:d];
//需要手动调用
[p didChangeValueForKey:@"dogs"];
[p removeObserver:self forKeyPath:@"dogs"];
输出结果
最后在做一个简单的UI变化。
在屏幕上放置一个按钮,两个不同颜色的view
@property (weak, nonatomic) IBOutlet UIView *blueView;
@property (weak, nonatomic) IBOutlet UIView *redView;
//观察redView的frame变化
[self.redView addObserver:self forKeyPath:@"frame" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:@"frameChanged"];
//将blueView的frame赋值给redView
- (IBAction)changeFrame:(id)sender {
[UIView animateWithDuration:1 animations:^{
self.redView.frame = self.blueView.frame;
}];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
NSLog(@"%@ %@ %@ %@ ",keyPath,[object class],change,context);
//观察到redView的frame发生变化,之后将redView旧的值赋值给blueView,效果是两个view交换位置
[UIView animateWithDuration:1 animations:^{
self.blueView.frame = [[change objectForKey:@"old"]CGRectValue];
}];
}
KVO总结:
- 在对象销毁前,要取消观察事件,否则会crash
- 对被观察对象的数组属性,数组元素的改变无法调用观察事件,需要开发者手动操作。
网友评论