KVO简介
全称是Key-value
observing,翻译成键值观察。提供了一种当其它对象属性被修改的时候能通知当前对象的机制。再MVC大行其道的Cocoa中,KVO机制很适合实现model和controller类之间的通讯。
设计思想
KVC/KVO是观察者模式的一种实现,在Cocoa中是以被万物之源NSObject类实现的NSKeyValueCoding/NSKeyValueObserving 非正式协议 的形式被定义为基础框架的一部分。 从协议的角度来说,KVC/KVO本质上是定义了一套让我们去遵守和实现的方法。
当然,KVC/KVO实现的根本是Objective-C的动态性和runtime,这在后文的原理部分会有详述。
另外,KVC/KVO机制离不开访问器方法的实现
它来源于设计模式中的观察者模式,其基本思想就是:
一个目标对象管理所有依赖于它的观察者对象,并在它自身的状态改变时主动通知观察者对象。这个主动通知通常是通过调用各观察者对象所提供的接口方法来实现的。观察者模式较完美地将目标对象与观察者对象解耦。
2、KVC/KVO实现原理
键值编码和键值观察是根据isa-swizzling技术来实现的,主要依据runtime的强大动态能力。下面的这段话是引自网上的一篇文章:
当某个类的对象第一次被观察时, 系统就会在运行期动态地创建该类的一个派生类,在这个派生类中重写基类中任何被观察属性的
setter 方法。
派生类在被重写的 setter
方法实现真正的通知机制 ,就如前面手动实现键值观察那样。这么做是基于设置属性会调用
setter 方法,而通过重写就获得了 KVO 需要的通知机制。当然前提是要通过遵循 KVO
的属性设置方式来变更属性值,如果仅是直接修改属性对应的成员变量,是无法实现 KVO
的。
同时派生类还重写了 class 方法以“欺骗”外部调用者它就是起初的那个类。然后系统将这个对象的 isa
指针指向这个新诞生的派生类,因此这个对象就成为该派生类的对象了,因而在该对象上对 setter 的调用就会调用重写的
setter,从而激活键值通知机制。此外,派生类还重写了 dealloc 方法来释放资源。
原文写的很好,还举了解释性的例子,大家可以去看看。
在我之前的一篇介绍Objective-C类和元类的文章:
对于类和元类我们只需要记住:类对象存的是关于实例对象的信息(变量,实例方法等),而元类对象(metaclass
object)中存储的是关于类的信息(类的版本,名字,类方法等)。要注意的是,类对象(class
object)和元类对象(metaclass
object)的定义都是objc_class结构,其不同仅仅是在用途上,比如其中的方法列表在类对象(instance
object)中保存的是实例方法(instance method),而在元类对象(metaclass
object)中则保存的是类方法(class method)。关于元类对象可以参考苹果官方文档" The Objective-‐C
Programming Language " 另外 一个很重要的一点是
(class
object) 是 (metaclass object)
的实例 (metaclass object) 是 (rootclass
object)的实例
而(rootclass object) 的指针是指向自己的 从而完成了类的继承 实例化
中介绍过,isa指针指向的其实是类的元类,如果之前的类名为:Person,那么被runtime更改以后的类名会变成:NSKVONotifying_Person。
新的 NSKVONotifying_Person类会重写以下方法:
增加了监听的属性对应的set方法,class,dealloc,_isKVOA。
①class
重写class方法是为了我们调用它的时候返回跟重写继承类之前同样的内容。
打印如下内容:
NSLog(@"self->isa:%@",self->isa);
NSLog(@"self class:%@",[self class]);
在建立KVO监听前,打印结果为:
self->isa:Person
self class:Person
在建立KVO监听之后,打印结果为:
self->isa:NSKVONotifying_Person
self class:Person
这也是isa指针和class方法的一个区别 ,大家使用的时候注意。
②重写set方法
新类会重写对应的set方法,是为了在set方法中增加另外两个方法的调用:
- (void)willChangeValueForKey:(NSString *)key
- (void)didChangeValueForKey:(NSString *)key
其中,didChangeValueForKey:方法负责调用:
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
方法, 这就是KVO实现的原理了!
如果没有任何的访问器方法,-setValue:forKey方法会直接调用:
- (void)willChangeValueForKey:(NSString *)key
- (void)didChangeValueForKey:(NSString *)key
如果在没有使用键值编码且没有使用适当命名的访问起方法的时候,我们只需要显示调用上述两个方法,同样可以使用KVO!
总结一下,想使用KVO有三种方法:
1)使用了KVC
使用了KVC,如果有访问器方法,则运行时会在访问器方法中调用will/didChangeValueForKey:方法;
没用访问器方法,运行时会在setValue:forKey方法中调用will/didChangeValueForKey:方法。
2)有访问器方法
运行时会重写访问器方法调用 will/didChangeValueForKey:方法。
因此,直接调用访问器方法改变属性值时,KVO也能监听到。
3)显示调用will/didChangeValueForKey:方法。
总之,想使用KVO,只要有 will/didChangeValueForKey:方法就可以了。
③ _isKVOA
这个私有方法估计是用来标示该类是一个 KVO
机制声称的类。
------------------------------实现demo
。-----------------------------------------------------------------
#import
"kvoViewController.h"
@interface kvoViewController ()
@property(nonatomic,retain)UISlider *sliderKvo;
@end
@implementation
kvoViewController
- (void)viewDidLoad
{
[super
viewDidLoad];
// Do any additional setup after
loading the view.
_sliderKvo = [[UISlider alloc]initWithFrame:CGRectMake(50,
300, 200,
30)];
_sliderKvo.value =
10.0f;
_sliderKvo.minimumValue = 1.0f;
_sliderKvo.maximumValue = 10.0f;
[_sliderKvo
addTarget:self action:@selector(slicderValueChange)
forControlEvents:UIControlEventValueChanged];
[_sliderKvo
addObserver:self forKeyPath:@"value" options:(NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld) context:@"kvoContext"];
[self.view addSubview:_sliderKvo];
}
- (void)didReceiveMemoryWarning
{
[super
didReceiveMemoryWarning];
//
Dispose of any resources that can be recreated.
}
#pragma mark - observer
FN
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
NSLog(@"keyPath :%@",keyPath);
NSLog(@"changeDic :%@",change);
NSLog(@"contextDic:%@",context);
}
-(void)slicderValueChange
{
[_sliderKvo
valueForKey:@"value"];
NSLog(@"slicderValueChange,:%f",_sliderKvo.value);
}
@end
kvc 的demo
假如 kvcTestViewController
在初始化的时候给 kvcPrivate,name赋值 那么
直接 valueForKey 的时候
就会给出初始化时候他们的值
(尽管他们俩是私有的也可以输出他们的值)如果 重新覆盖了一次 valueForKey
同名valueForKey
则会输出新的值
kvcTestViewController
*kvcTest =
[[kvcTestViewController alloc]init];
[kvcTest setValue:@"kvcPrivate" forKey:@"name"];
NSLog(@"valueForKey :%@",[kvcTest valueForKey:@"kvcPrivate"]);
NSLog(@"name:%@",[kvcTest valueForKey:@"name"]);
总结
值得注意的是:不要忘记解除注册,否则会导致资源泄露。
KVO 并不是什么新事物,换汤不换药,它只是观察者模式在 Objective C 中的一种运用,这是 KVO
的指导思想所在。其他语言实现中也有“KVO”,如 WPF 中的 binding。而在 Objective C 中又是通过强大的
runtime 来实现自动键值观察的。至此,对 KVO 的使用以及注意事项,内部实现都介绍完毕,对 KVO
的理解又深入一层了。Objective 中的 KVO 虽然可以用,但却非完美,有兴趣的了解朋友请查看《KVO
的缺陷》 以及改良实现 MAKVONotificationCenter 。
文章参考链接
: http://www.tuicool.com/articles/M7vQRj
http://blog.csdn.net/kesalin/article/details/8194240
Key-value
observing:官方文档
Key-Value
Observing Done Right : 官方 KVO
实现的缺陷
MAKVONotificationCenter :
一个改良的 Notification 实现,托管在 GitHub 上
Friday Q&A
2009-01-23
深入浅出Cocoa 之动态创建类
深入浅出Cocoa之类与对象
加载中,请稍候......