Objective-C:KVO
@import url(http://i.cnblogs.com/Load.ashx?type=style&file=SyntaxHighlighter.css);
@import url(/css/cuteeditor.css);
KVO(键值观察)是Objective-C提供的一种观察对象属性变化的机制,其内部是利用KVC技术来实现观察者设计模型。利用KVO用户可以注册一个对象为另一个对象的观察者,并在被观察对象的属性发生变化时能收到通知。
1 使用KVO
利用键值观察(Key Value Observing),可以自动观察其他对象的变化。因此,当一个对象改变状态(属性)时,其它对象就会得到通知。通过键值观察,你不需要手动告诉其他对象进行更新。它们会自动收到新值并执行适当的操作。这极其强大。设置是该技术最强大的应用之一,此外, Cocoa 框架中的核心数据和其他技术也利用了键值观察实现了一些奇妙的功能。
要使用KVO需要进行一些操作和配置:
1) 要使用键值观察,被观察的对象必须对所观察的特性(属性)使用符合 KVC 标准的存取器方法。
2) 想要观察变化的对象,也就是观察者,必须实现一个接收通知的回调方法。该方法是-observeValue:forKeyPath:ofObject:change:context:。该方法在值变化时被调用并可以配置成同时接收新值和原值以及其他自定义的信息。
3) 观察者通过调用-addObserver:forKeyPath:options:context:方法针对被观察对象进行注册。调用该方法,告诉对象要观察的 KVC 键路径以及希望看到的变化,并提供一个在收到变化通知时可以传回的上下文对象。
注意观察者完成这些配置后,键路径指定的属性的任何变化都可以自动调用观察者的回调方法。在观察者完成对被观察对象的观察后,必须将自己移除。如果没有做到这点并且观察者之后就释放了,将来向该观察者发送通知时可能会导致应用崩溃。
2 注册成为观察者
注册成为观察者很容易。针对想要观察的对象简单调用-addObserver:forKeyPath:options:context:方法,
1 [obj addObserver:self
2 forKeyPath:@”memberVariable”
3 options:(NSKeyValueObservingOptionNiew | NSKeyValueObservingOptionOld)
4 context:NULL];
Observer 参数通常是 self,这是在被观察值变化时收到通知的对象。键路径参数指定想要观察的特性的键路径。 options 参数指定一些标记来告诉 KVO 你希望变化如何传给你。这些值可以通过|操作符进行或操作。传入的可能值如表 21所示。
表 21 传入的可能值
值 |
功 能 |
NSKeyValueObservingOptionNew |
作为变更信息的一部分发送新值 |
NSKeyValueObservingOptionOld |
作为变更信息的一部分发送旧值 |
NSKeyValueObservingOptionInitial |
在观察者注册时发送一个初始更新 |
NSKeyValueObservingOptionPrior |
在变更前后分别发送变更,而不只在变更后发送一次 |
上下文参数是一个在 KVO系统中无变更传递的void*参数,即当被观察的属性发生变化时,会被传回给观测对象的回调方法。本质上,就 KVO 而言,该参数是不透明的数据块,任何从此传入参数的都会无变更传递的。
注意:
记住使用 void*上下文参数时有和垃圾回收相关的特殊规则,你必须确保 void*指向的数据在之后访问时仍然没有被释放并有效。换句话说,不要将一些存储在栈上的值传递给该参数,这会导致崩溃。
3 实现KVO的回调方法
使用 KVO 的第二步就是编写观察者的回调方法。如下的实现代码显示了-observeValue:forKeyPath:ofObject:change:context:方法的一个示例实现。
1 -(void)observeValuseForKeyPath: (NSString*)inKeyPath
2 ofObject: (id)inObject
3 change: (NSDictionary*)inChange
4 context: (void*)inCtx
5 {
6 if(inKeyPath isEqualToString:@”memverVariable”)
7 {
8 NSString *newValue = [inChange objectForKey:NSKeyValuseChangeNewKey];
9 }
10 else if([inKeyPath isEqualToString:@”…”])
11 {
12 …
13 }
14 }
可以从该方法看出,要做的第一件事情就是找出被观察对象中变化的特性。该方法自动传入一个对象参数,告诉你哪个对象向你发送通知。通过对键路径的传入值使用-isEquals 方法,你可以准确地确定对象的什么特性发生了改变。 Key 参数仅仅是一个字符串,和对 KVC 使用时一样。因此,可以使用 NSString 方法-isEqualToString:来确定该通知所对应的键路径。
在确定了对象的哪个特性发生变化后,你可以执行任何合适的操作。实际的变化通过 change参数传递给你。该参数是一个 NSDictionary 对象,包括和你注册成为观察者时所请求的变化信息相关的键和值。这些键和值如表 22所示。
表 22 和变化信息相关的键值
键 |
值 |
NSKeyValueChangeKindKey |
指定变化类型的 NSNumber |
NSKeyValueChangeNewKey |
新值 |
NSKeyValueChangeOldKey |
原值 |
NSKeyValueChangeIndexeskey |
如果 NSKeyValueChangeKindKey 是 NSKeyValueChangeInsertion、 NSKeyValueChangeRemoval、NSKeyValueChangeReplacement 之一,该值就包含变化值的索引 |
NSKeyValueChangeNotificationIsPriorKey |
和 NSKeyValueChangeOptionPrior 结合使用来表示这是"之前"的通知 |
可以看出,如果选择同时接收原值和新值,两个都会在变化参数中提供,通过合适的键就可以访问到。从变化字典中获取到值之后,就可以在对象中使用它执行任何需要的操作。
记住 KVC 必须使用对象来发送值——不能直接使用标量和结构体。因此,如果所观察的值是标量或者结构体,所接收的值就分别是 NSNumber 或 NSValue 类型的。因此,必须从该值中提取出实际需要的标量或者结构体值。上述示例代码就展示了这一点。
NSKeyValueChangeKindKey 指定了接收到的变化类型。可能的类型如表 23所示。
表 23 可能的变化类型
值 |
功 能 |
NSKeyValueChangeSetting |
指定该值被设置 |
NSKeyValueChangeInsertion |
指定这些值插入到集合或者一对多的关系中 |
NSKeyValueChangeRemoval |
指定这些值在一对多的关系中被移除 |
NSKeyValueChangeReplacement |
指定这些值在一对多的关系中被替换 |
4 移除观测者
记住在结束对一个对象变化的观察后,需要移除观察者。如果不这样,应用可能会崩溃。
为了移除观察者,只需要调用方法-removeObserver:forKeyPath:,并传入观察者作为第一个参数,观察的键路径是第二个参数。代码清单 6-16 显示了一个在观察者的 dealloc 方法中实现的示例。
1 -(void) dealloc:
2 {
3 [obj removeObserver: self forKeyPath:@”memberVariable”];
4 [super dealloc];
5 }
说明:
在垃圾回收的环境中,如果忘记移除观察者可能不会造成崩溃。但是,移除观察者仍是一种好的做法,这样就可以在不支持垃圾回收的环境中形成该习惯。
5 实现手动通知
所有的这些通知都自动发生。需要做的就是为属性提供符合 KVC 标准的存取器方法,其他一切都会正常工作。有时,不一定想利用自动通知。有时想在改变一个值或者一组值时手动发送通知。比如,如果需要一次性进行很多变更,可能会想将这些变化打包后一起发送。在这些情况下就会使用手动通知。
为了实现手动通知,必须重写类方法+automaticallyNotifiesObserversForKey:来告诉 Objective-C 你不想自动通知观察者所发生的变化。可以通过对任意一个想进行手动通知的键返回 NO 来实现。示例如代码清单 6-17 所示。
1 +(BOOL)automaticallyNotifiesObserversForKey:(NSString*)inKey
2 {
3 if([inKey isEqualToString:@”memberVariable”])
4 return NO;
5 return YES;
6 }
如果想要手动通知所发生的变化,你必须在变化之前调用方法-willChangeValueForKey:,然后在变化之后调用方法-didChangeValueForKey:。示例如代码清单 6-18 所示。
1 -(void)setMemberVariable:(CGFloat)inValue
2 {
3 [self willChangeValueForKey:@”memberVariable”];
4 memberVariable = inValue;
5 [self didChangeValueForKey:@”memberVariable”];
6 }
这些调用在需要时是可以嵌套的,比如在一次调用中需要修改多个变量的情况。此外还有和一对多关系对应的调用。它们是-willChange:valuesAtIndexes:forKey:和-didChange:valuesForIndexes:forKey:。
6 使用KVO的风险
使用 KVO 也会遇到问题,更具体点说,使用 KVO 最大的风险就是如果观察者观察每一步,这些观察者可能会执行其他操作,因为你无法控制这些观察者,所以也就无法控制这些操作。
大多数情况下这不会成为一个问题,但也在例外。这种情况就是在初始化函数或者 dealloc函数中使用存取器方法来释放变量成员时,如代码清单 6-19 所示。
1 -(void)dealloc
2 {
3 [self setFoo:nil];
4 [self setBar:nil];
5 [super dealloc];
6 }
按这种方式写 dealloc 方法很好!可以在释放成员变量的同时将它设成nil,一步搞定。问题就是,在调用这些存取器方法时, KVO 观察者会在这些变化发生时收到通知。如果他们不想接收 nil 或者希望在收到通知时能够处理对象本身,此时就会发生一些糟糕的事情。此外,如果想到了观察者在收到 bar 变量的变化通知时,希望可以访问 foo 变量,这种情况下就会造成一个问题,因为 foo 变量已经被释放并且被设置成 nil。
苹果目前推荐的做法就是不要在初始化函数或者 dealloc 方法中通过存取器方法初始化和释放成员变量。这种情况在 64 位运行时中变得更复杂,因为它可以在没有相关的成员变量的情况下声明属性。在这些情况中,初始化和释放成员变量的唯一办法就是通过存取器方法。
我是使用存取器方法来初始化和释放成员变量的,除非我知道在给定的环境中这样做会造成问题。此外,实现键值观察者时,我会确保观测者可以正确处理 nil 值并尽量最小化副作用。
如果你觉得这种风险是值得的,那就通过存取器方法来编写初始化函数和释放函数吧。只要意识到可能的危险,在遇到问题时,就可以马上知道应该从哪里查找。另一方面,如果你不能确保观察者会这么做的话,那就就遵循苹果的建议,除非不得已,否则不要在初始化函数和析构函数中使用存取器方法。
Objective-C:KVO的更多相关文章
- UI:KVO、KVC
什么是KVC 什么是 KVO ? KVC:(NSKey ValueCoding)”键-值 编码“是一种间接的访问对象属性(字符串表征)的机制.对象的属性都可以通过使用KVC机制用相同的方式访问.我们 ...
- KVO的使用二:常用方法及小技巧
(文章及代码接上一篇) options详解: KVO的注册方法中有一个options枚举,用来确定观察者的接收消息方法接收的信息,那么具体有什么关联呢?下面通过一段代码来验证是如何关联的.依次选择op ...
- ios开发runtime学习五:KVC以及KVO,利用runtime实现字典转模型
一:KVC和KVO的学习 #import "StatusItem.h" /* 1:总结:KVC赋值:1:setValuesForKeysWithDictionary实现原理:遍历字 ...
- iOS - 详细理解KVC与KVO
详细理解KVC与KVO 在面试的时候,KVC与KVO有些时候还是会问到的,并且他们都是Objective C的关键概念,在这里我们先做一个简单地介绍: (一)KVC: KVC即指:NSKeyValue ...
- KVO机制浅析和实例演示
什么是KVO? KVO是Key-Value-Observing的缩写,通过KVO这种机制对象可以通过它得到其他对象的某个属性的变更通知.这种机制在MVC模式下显得更为重要,KVO可以让视图对象经过控制 ...
- [深入浅出Cocoa]详解键值观察(KVO)及其实现机理
一,前言 Objective-C 中的键(key)-值(value)观察(KVO)并不是什么新鲜事物,它来源于设计模式中的观察者模式,其基本思想就是: 一个目标对象管理所有依赖于它的观察者对象,并在它 ...
- iOS---观察者模式之--->KVO
文章结构如下: Why? (为什么要用KVO) What? (KVO是什么) How? ( KVO怎么用) More (更多细节) 原理 自己实现KVO 在我的上一篇文章浅谈 iOS Notifica ...
- KVC 和 KVO
KVC 键值编码 全称是Key-value coding,翻译成键值编码.它提供了一种使用字符串而不是访问器方法去访问一个对象实例变量的机制. 1.通过key(成员变量的名称)设置 ...
- 【原】Github系列之一:一起做仿天气类应用中的实时模糊效果LiveBlur
从本文开始,我将专门开辟一个Github Code系列,开源自己写的一部分有意思而且实用的demo,共同学习.以前都发布在git OSChina上,后面有空会陆陆续续整理到Github上.OSChin ...
随机推荐
- Android Audio 分析
一.架构 二.MediaServer初始化 所有的media服务都在进程mediaserver里.其代码在framework/base/media/mediaserver/main_mediaserv ...
- android小知识
string 与 []byte 互转: public String BytesToString(byte[] data) { return new String(data); } public byt ...
- HDU 2993 MAX Average Problem dp斜率优化
MAX Average Problem Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Othe ...
- QQ 群也能接收告警啦!团队沟通力 Up Up!
截止到昨天,你已经可以通过 OneAlert 的「排班」和「分派」功能,来对告警进行有序地分发,解决团队协作效率低的问题了.然而 OneAlert 觉得自己还可以更进一步,把团队沟通困难的问题也解决掉 ...
- Qt学习笔记网络(URL和下载的功能都有)
http://www.cnblogs.com/li-peng/p/3656613.html
- Android如何正确的保存文件
在Android 官方开发文档中有一篇文档来介绍如何保存应用的数据,但笔者用过很多程序(从知名的到不知名的)处理的都不是很完美,或者 没有按照Android 开发团队建议的方式去保存他们应用的数据.当 ...
- 【HDOJ】3789 奥运排序问题
写了个函数指针,这题目很水,但是佷烦. #include <iostream> #include <cstring> #include <cstdio> #incl ...
- Visual Studio 2013新功能
微软打破了Visual Studio两年升级一次的传统,Visual Studio 2012发布还不足一年,微软就计划发布了Visual Studio 2013了.在今天的TechEd大会上,微软宣布 ...
- Screen-Space Bent Cones (SSBC) in Unity5
噪音少.高度保留了纹理细节 博主近期渲染:最近用unity5弄的一些渲染 ---- by wolf96 http://blog.csdn.net/wolf96
- 游戏开发设计模式之对象池模式(unity3d 示例实现)
前篇:游戏开发设计模式之命令模式(unity3d 示例实现) 博主才学尚浅,难免会有错误,尤其是设计模式这种极富禅意且需要大量经验的东西,如果哪里书写错误或有遗漏,还请各位前辈指正. 原理:从一个固定 ...