一、KVO
KVO的是KeyValue Observe的缩写,中文是键值观察。这是一个典型的观察者模式,利用它可以很容易使用实现视图组件和数据模型的分离,当数据模型的属性值改变之后作为监听器的视图组件就会被激发,激发时就会回调监听器自身,在Objc中实现KVO则必须实现NSKeyValueObServing协议,不过幸运的是NSObject已经实现了该协议,因此几乎所哟的NSObjectd对象都可以使用KVO。iOS中有个Notification的机制,也可以获得通知,但这个机制需要有个Center,相比之下KVO更加简洁而直接。
KVO的使用也很简单,就是简单的3步。
1.注册需要观察的对象的属性addObserver:forKeyPath:options:context:
2.实现observeValueForKeyPath:ofObject:change:context:方法,这个方法当观察的属性变化时会自动调用
3.取消注册观察removeObserver:forKeyPath:context:
这里有两种方式,一种是匹配keyPath,另一种是使用context
Demo:事列
A.注册需要观察的对象
- (void)viewDidLoad
{
[super viewDidLoad];
// 注册监听
[self.moveView addObserver:self forKeyPath:@"frame" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
self.p.name = @"小明";
[self.p addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:PersonAgeContext];
}
B.实现
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
// 方式1.匹配keypath
if ([keyPath isEqualToString:@"frame"]) {
NSLog(@"self.moveView.y = %f", self.moveView.y);
}
// 方式2.上下文
if (context == PersonAgeContext) {
NSLog(@"%@%d岁了", self.p.name, self.p.age);
}
}
C.移除监听
-(void)dealloc // ARC模式下
{
[self.moveView removeObserver:self forKeyPath:@"frame"];
[self.p removeObserver:self forKeyPath:@"age" context:PersonAgeContext];
}
KVO的 优势 :
1.能够提供一种简单的方法实现两个对象间的同步。例如:model和view之间同步;
2.能够对非我们创建的对象,即内部对象的状态改变作出响应,而且不需要改变内部对象(SKD对象)的实现;
3.能够提供观察的属性的最新值以及先前值;
4.用key paths来观察属性,因此也可以观察嵌套对象;
5.完成了对观察对象的抽象,因为不需要额外的代码来允许观察值能够被观察
缺点 :
1.我们观察的属性必须使用strings来定义。因此在编译器不会出现警告以及检查;
2.对属性重构将导致我们的观察代码不再可用;
3.复杂的“IF”语句要求对象正在观察多个值。这是因为所有的观察代码通过一个方法来指向;
4.当释放观察者时不需要移除观察者。
二、KVC
KVC的常用方法:
- (id)valueForKey:(NSString *)key; -(void)setValue:(id)value forKey:(NSString *)key;
valueForKey的方法根据key的值读取对象的属性,setValue:forKey:是根据key的值来写对象的属性。
注意:
(1). key的值必须正确,如果拼写错误,会出现异常
(2). 当key的值是没有定义的,valueForUndefinedKey:这个方法会被调用,如果你自己写了这个方法,key的值出错就会调用到这里来
(3). 因为类key反复嵌套,所以有个keyPath的概念,keyPath就是用.号来把一个一个key链接起来,这样就可以根据这个路径访问下去
(4). NSArray/NSSet等都支持KVC
KVC过程讲解:
以 [object setValue:@"134567" forKey:@"uid"];为例子,来探究KVC的实现过程
第一步:搜索
1、首先搜索setKey:方法.(key指成员变量名, 首字母大写)
2、上面的setter方法没找到, 如果类方法accessInstanceVariablesDirectly返回YES. 那么按 _key, _isKey,key, iskey的顺序搜索成员名.(NSKeyValueCodingCatogery中实现的类方法, 默认实现为返回YES)
3、如果没有找到成员变量, 调用setValue:forUnderfinedKey:
第二步:编译器处理
被编译器处理后:
// 首先找到对应sel
SEL sel = sel_get_uid("setValue:forKey:");
// 根据object->isa找到sel对应的IMP实现指针
IMP method = objc_msg_lookup (object->isa,sel);
// 调用指针完成KVC赋值
method(object, sel, @"134567", @"uid");
解释:
1.先根据方法名通过C函数sel_get_uid拿到选择子sel
2.使用C函数objc_msg_lookup通过对象指针,选择子获取函数实现指针
3. 调用C函数method(object, sel, @"134567", @"uid"),实现KVC赋值
引申:setValue和setObject区别
setObject:ForKey: 是NSMutableDictionary特有的;setValue:ForKey:是KVC的主要方法
setobject中的key和value可以为除了nil外的任何对象
setValue中的key只能为字符串 value可以为nil也可以为空对象[NSNull null]以及全部对象