加载中…
个人资料
  • 博客等级:
  • 博客积分:
  • 博客访问:
  • 关注人气:
  • 获赠金笔:0支
  • 赠出金笔:0支
  • 荣誉徽章:
正文 字体大小:

object-c  - kvo实现原理

(2014-02-24 16:32:07)
分类: Mac/IOS那些事

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之类与对象


0

阅读 收藏 喜欢 打印举报/Report
  

新浪BLOG意见反馈留言板 欢迎批评指正

新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 产品答疑

新浪公司 版权所有