原文链接:[原创]obj-c编程17:键值观察(KVO)

系列专栏链接:objective-c 编程系列

说完了前面一篇KVC,不能不说说它的应用KVO(Key-Value Observing)喽。KVO类似于ruby里的hook功能,就是当一个对象属性发生变化时,观察者可以跟踪变化,进而观察或是修正这个变化,这是通过回调观察者注册的回调函数来完成的。要使用键值观察,必须满足3个条件:

1 被观察对象必须对所观察属性使用符合KVC标准的存取器方法;

2 观察者必须实现接受通知的方法(回调方法):-observeValue:forKeyPath:ofObject:change:context:,该方法在值发生变化时被调用,并且可以定制行为:比如同时接收新值和原值以及其他自定义信息;

3 观察者通过-addObserver:forKeyPath:options:context:方法对特定对象的特定路径进行注册.

还要注意的是,在观察者完成对被观察者的观察后,必须将自己移除,否则当观察者释放后,来自被观察者的变化通知消息将无处可发,可能会导致引用崩溃。另外可以使用多个观察者对同一个对象的同一个路径进行观察啊,类似于形成一个观察链。

下面我将会写一个简单的例子,用代码说明上述内容:

  1. #import <Foundation/Foundation.h>
  2.  
  3. #define msg(...) NSLog(__VA_ARGS__)
  4. #define mki(x) [NSNumber numberWithInt:x]
  5.  
  6. @interface Son:NSObject{
  7. NSString *girl_friend_name;
  8. }
  9. @property NSString *girl_friend_name;
  10. -(void)think;
  11. -(void)fall_in_love_with:(NSString *)girl_name;
  12. @end
  13.  
  14. @implementation Son
  15. @synthesize girl_friend_name;
  16.  
  17. -(void)fall_in_love_with:(NSString *)girl_name{
  18. self.girl_friend_name = girl_name;
  19. }
  20.  
  21. -(void)think{
  22. msg(@"My love girl is %@ ... Does baba know?",girl_friend_name);
  23. }
  24. @end
  25.  
  26. @interface Baba:NSObject{
  27. Son *son;
  28. }
  29. @property Son *son;
  30. @end
  31.  
  32. @implementation Baba
  33. @synthesize son;
  34.  
  35. -(id)initWithSon:(Son *)son_v{
  36. self = [super init];
  37. if(self){
  38. son = son_v;
  39. [son addObserver:self forKeyPath:@"girl_friend_name" \
  40. options:(NSKeyValueObservingOptionNew|\
  41. NSKeyValueObservingOptionOld|NSKeyValueObservingOptionInitial) \
  42. context:nil];
  43. }
  44. return self;
  45. }
  46.  
  47. -(void)observeValueForKeyPath:(NSString *)key_path \
  48. ofObject:(id)obj change:(NSDictionary *)change \
  49. context:(void *)context{
  50.  
  51. NSString *old_name = [change objectForKey:NSKeyValueChangeOldKey];
  52. NSString *new_name = [change objectForKey:NSKeyValueChangeNewKey];
  53.  
  54. if(!old_name){
  55. //NSKeyValueObservingOptionInitial选项的作用,第一次hook会发一次消息
  56. //以后每次更改会发一次消息。
  57. msg(@"Yes , It's first time to induction son's girl_name!!!");
  58. }else{
  59. if([new_name isEqualToString:@"libinbin"]){
  60. [obj fall_in_love_with:@"fanbinbin"];
  61. }
  62. msg(@"My son's %@ change from %@ to %@ ... That's OK? :(",\
  63. key_path,old_name,new_name);
  64. }
  65. }
  66.  
  67. -(void)dealloc{
  68. [son removeObserver:self forKeyPath:@"girl_friend_name"];
  69. son = nil;
  70. //[super dealloc];
  71. }
  72. @end
  73.  
  74. int main(int argc,char *argv[])
  75. {
  76. @autoreleasepool{
  77. Son *son = [[Son alloc] init];
  78. [son fall_in_love_with:@"lili"];
  79. [son think];
  80.  
  81. Baba *baba = [[Baba alloc] initWithSon:son];
  82. [son fall_in_love_with:@"mimi"];
  83. [son think];
  84.  
  85. //儿子本来喜欢的是libinbin,可是...
  86. [son fall_in_love_with:@"libinbin"];
  87. //被他老爸用特异功能改成fanbinbin啦!
  88. [son think];
  89. }
  90. return ;
  91. }

简单说明下:老爸自然是要随时监控儿子的女朋友啊,可是这个老爸监视也就算了,还强行改变儿子的女朋友,这个就...随便想的一个例子,就当fun吧。说明下主要方法:

-addObserver注册对象的观察者,其中的options的其他可选值如下,可以多选:

其中的context参数意义不明哦,如果哪位知道的,别忘了告诉老猫我一下啊。NSKeyValueObservingOptionInitial标志的含义参见代码注释吧。

-observeValueForKeyPath回调方法中的change是一个字典参数,其中包括:

其中NSKeyValueChangeKindKey指定了接收到得变化类型,可能有以下几种值:

书上的代码在该方法定义的末尾部分增加了其对父类中同名方法的回溯调用:

[super observeValueForkeyPath:key_path ofObject:obj change:change context:ctx];

不过我在代码中没有写这个。书上在观察者的dealloc方法最后是有一句[super dealloc],但是这招在ARC下编译时会出错喽,暂时去掉吧。该段代码运行结果如下:

  1. apple@kissAir: objc_src$./k
  2.  
  3. -- ::26.757 k[:] My love girl is lili ... Does baba know?
  4.  
  5. -- ::26.759 k[:] Yes , It's first time to induction son's girl_name!!!
  6.  
  7. -- ::26.760 k[:] My son's girl_friend_name change from lili to mimi ... That's OK? :(
  8.  
  9. -- ::26.760 k[:] My love girl is mimi ... Does baba know?
  10.  
  11. -- ::26.761 k[:] My son's girl_friend_name change from libinbin to fanbinbin ... That's OK? :(
  12.  
  13. -- ::26.761 k[:] My son's girl_friend_name change from mimi to libinbin ... That's OK? :(
  14.  
  15. -- ::26.761 k[:] My love girl is fanbinbin ... Does baba know?

爸爸监管的不错,可是儿子不高兴喽。hack儿子如何能让老爸如此侵犯隐私啊?咋办呢?不自动传递变更通知不就结了!为了实现手动通知,需要重写Son的类方法+automaticallyNotifiesObserversForKey,来告知obj-c你不想自动通知观察者自己的变化,注意是类方法哦!可以通过对特定的键名返回NO来完成:

  1. +(BOOL)automaticallyNotifiesObserversForKey:(NSString *)key{
  2. if([key isEqualToString:@"girl_friend_name"])
  3. return NO;
  4. return YES;
  5. }

在Son类的实现中加入以上类方法,重新编译运行:

  1. apple@kissAir: objc_src$./k
  2.  
  3. -- ::36.726 k[:] My love girl is lili ... Does baba know?
  4.  
  5. -- ::36.729 k[:] Yes , It's first time to induction son's girl_name!!!
  6.  
  7. -- ::36.729 k[:] My love girl is mimi ... Does baba know?
  8.  
  9. -- ::36.730 k[:] My love girl is libinbin ... Does baba know?

老爸”感应“不到喽!但是这样长了老爸会怀疑的啦,肿么宝贝儿子从不谈恋爱呢?这个不行哦!于是乎聪明的hack儿子可以再手动的通知老爸篡改后的内容啊!具体为:在Son属性写者方法的变化之前调用-willChangeValueForKey,然后在变化之后调用-didChangeValueForKey,重新改写写者方法吧,修改后完整代码如下:

  1. #import <Foundation/Foundation.h>
  2.  
  3. #define msg(...) NSLog(__VA_ARGS__)
  4. #define mki(x) [NSNumber numberWithInt:x]
  5.  
  6. @interface Son:NSObject{
  7. NSString *girl_friend_name;
  8. }
  9. //在属性默认为atomic的情况下,如果自定义写者方法必须同时自定义读者方法,
  10. //或者将属性改为nonatomic模式。
  11. @property(nonatomic) NSString *girl_friend_name;
  12. -(void)think;
  13. -(void)fall_in_love_with:(NSString *)girl_name;
  14. @end
  15.  
  16. @implementation Son
  17. @synthesize girl_friend_name;
  18.  
  19. -(void)fall_in_love_with:(NSString *)girl_name{
  20. self.girl_friend_name = girl_name;
  21. }
  22.  
  23. -(void)think{
  24. msg(@"My love girl is %@ ... Does baba know?",girl_friend_name);
  25. }
  26.  
  27. +(BOOL)automaticallyNotifiesObserversForKey:(NSString *)key{
  28. if([key isEqualToString:@"girl_friend_name"])
  29. return NO;
  30. return YES;
  31. }
  32.  
  33. -(void)setGirl_friend_name:(NSString *)name{
  34. [self willChangeValueForKey:@"girl_friend_name"];
  35. //让老爸以为自己和女学霸谈恋爱哦
  36. girl_friend_name = @"女学霸";
  37. [self didChangeValueForKey:@"girl_friend_name"];
  38. //恢复本色吧,BAD BOY... :)
  39. girl_friend_name = name;
  40. }
  41. @end
  42.  
  43. @interface Baba:NSObject{
  44. Son *son;
  45. }
  46. @property Son *son;
  47. @end
  48.  
  49. @implementation Baba
  50. @synthesize son;
  51.  
  52. -(id)initWithSon:(Son *)son_v{
  53. self = [super init];
  54. if(self){
  55. son = son_v;
  56. [son addObserver:self forKeyPath:@"girl_friend_name" \
  57. options:(NSKeyValueObservingOptionNew|\
  58. NSKeyValueObservingOptionOld|NSKeyValueObservingOptionInitial) \
  59. context:nil];
  60. }
  61. return self;
  62. }
  63.  
  64. -(void)observeValueForKeyPath:(NSString *)key_path \
  65. ofObject:(id)obj change:(NSDictionary *)change \
  66. context:(void *)context{
  67.  
  68. NSString *old_name = [change objectForKey:NSKeyValueChangeOldKey];
  69. NSString *new_name = [change objectForKey:NSKeyValueChangeNewKey];
  70.  
  71. if(!old_name){
  72. //NSKeyValueObservingOptionInitial选项的作用,第一次hook会发一次消息
  73. //以后每次更改会发一次消息。
  74. msg(@"Yes , It's first time to induction son's girl_name!!!");
  75. }else{
  76. if([new_name isEqualToString:@"libinbin"]){
  77. [obj fall_in_love_with:@"fanbinbin"];
  78. }
  79. msg(@"My son's %@ change from %@ to %@ ... That's OK? :(",\
  80. key_path,old_name,new_name);
  81. }
  82. }
  83.  
  84. -(void)dealloc{
  85. [son removeObserver:self forKeyPath:@"girl_friend_name"];
  86. son = nil;
  87. //[super dealloc];
  88. }
  89. @end
  90.  
  91. int main(int argc,char *argv[])
  92. {
  93. @autoreleasepool{
  94. Son *son = [[Son alloc] init];
  95. [son fall_in_love_with:@"lili"];
  96. [son think];
  97.  
  98. Baba *baba = [[Baba alloc] initWithSon:son];
  99. [son fall_in_love_with:@"mimi"];
  100. [son think];
  101.  
  102. //儿子本来喜欢的是libinbin,可是...
  103. [son fall_in_love_with:@"libinbin"];
  104. //被他老爸用特异功能改成fanbinbin啦!
  105. [son think];
  106. }
  107. return ;
  108. }

编译运行结果如下:

  1. apple@kissAir: objc_src$./k
  2.  
  3. -- ::59.284 k[:] My love girl is lili ... Does baba know?
  4.  
  5. -- ::59.286 k[:] Yes , It's first time to induction son's girl_name!!!
  6.  
  7. -- ::59.287 k[:] My son's girl_friend_name change from lili to 女学霸 ... That's OK? :(
  8.  
  9. -- ::59.287 k[:] My love girl is mimi ... Does baba know?
  10.  
  11. -- ::59.288 k[:] My son's girl_friend_name change from mimi to 女学霸 ... That's OK? :(
  12.  
  13. -- ::59.288 k[:] My love girl is libinbin ... Does baba know?

这回该老爸傻傻的以为儿子一直在和女学霸贪恋喽,各位童鞋当年也是这样糊弄老爸老妈的吧,嘿嘿。

[原创]obj-c编程17:键值观察(KVO)的更多相关文章

  1. obj-c编程17:键值观察(KVO)

    说完了前面一篇KVC,不能不说说它的应用KVO(Key-Value Observing)喽.KVO类似于ruby里的hook功能,就是当一个对象属性发生变化时,观察者可以跟踪变化,进而观察或是修正这个 ...

  2. OC键值观察KVO

    什么是KVO? 什么是KVO?KVO是Key-Value Observing的简称,翻译成中文就是键值观察.这是iOS支持的一种机制,用来做什么呢?我们在开发应用时经常需要进行通信,比如一个model ...

  3. 键值观察 KVO

    http://www.cnblogs.com/dyf520/p/3805297.html Key-Value Observing Programming Guide 1,注册Key-Value Obs ...

  4. 《苹果开发之Cocoa编程》键-值编码和键-值观察

    一.KVC 键-值编码(Key - Value Coding, KVC)是通过变量名的读取和设置变量值的一种方法,将字符串的变量名作为key来引用.NSObject定义了两个方法(KVC方法)用于变量 ...

  5. 路径(keyPath)、键值编码(KVC)和键值观察(KVO)

    键路径 在一个给定的实体中,同一个属性的所有值具有相同的数据类型. 键-值编码技术用于进行这样的查找—它是一种间接访问对象属性的机制. - 键路径是一个由用点作分隔符的键组成的字符串,用于指定一个连接 ...

  6. 09 (OC)* 键路径(keyPath)、键值编码(KVC)、键值观察(KVO)

    键路径在一个给定的实体中,同一个属性的所有值具有相同的数据类型.键-值编码技术用于进行这样的查找—它是一种间接访问对象属性的机制. - 键路径是一个由用点作分隔符的键组成的字符串,用于指定一个连接在一 ...

  7. [原创]obj-c编程16:键值编码(KVC)

    原文链接:obj-c编程16:键值编码(KVC) 我们可以借助obj-c中的键值编码(以后简称KVC,Key-Value Coding)来存取类的属性,通过指定所要访问的属性名字符串标示符,可以使用存 ...

  8. [深入浅出Cocoa]详解键值观察(KVO)及其实现机理

    一,前言 Objective-C 中的键(key)-值(value)观察(KVO)并不是什么新鲜事物,它来源于设计模式中的观察者模式,其基本思想就是: 一个目标对象管理所有依赖于它的观察者对象,并在它 ...

  9. IOS设计模式第八篇之键值观察模式

    版权声明:原创作品,谢绝转载!否则将追究法律责任. 键值观察模式: 在KVO,一个对象可以要求被通知当他的某个特殊的属性被改变了.自己或者另一个对象.如果你感兴趣你可以阅读更多的信息参考: Apple ...

随机推荐

  1. (转)C# WinForm中 获得当前鼠标所在控件 或 将窗体中鼠标所在控件名显示在窗体标题上

    原文地址:http://www.cnblogs.com/08shiyan/archive/2011/04/14/2015758.html /********************** * 课题:将窗 ...

  2. zoj1108 FatMouse's Speed

    给你每个物体两个参数,求最长的链要求第一个参数递增,第二个参数递减,要求输出任意最长路径. 首先第一反应根据第二个参数排个序,然后不就是最长上升子序列的问题吗? O(nlogn)的复杂度,当然这样可以 ...

  3. zoj1074 To the Max

    题目很简单,求一个连续的最大子矩阵的值. zoj上的数据非常弱. 首先爆搜是O(N^4),10^8的复杂度略高,那么我们可以处理一下其中一维的前缀和,降一阶,然后按照连续最大子序列来处理,因为可能为负 ...

  4. typedef用法总结。

    引用贴:http://www.cnblogs.com/csyisong/archive/2009/01/09/1372363.html 首先#define为预处理,与typedef是完全不同的机制.详 ...

  5. 整理mysql的一些常用用法

    在php项目中,使用mysql的一些常用的语句,今天有空系统整体一下.有些整理自网络,如有错误,请指正,谢谢.... #显示数据库和显示数据表show databases;use databaseNa ...

  6. 【git】error: Your local changes to the following files

    今天在服务器上git pull是出现以下错误: error: Your local changes to the following files would be overwritten by mer ...

  7. 欧几里得求最大公约数--JAVA递归实现

    欧几里得算法求最大公约数算法思想: 求p和q的最大公约数,如果q=0,最大公约数就是p:否则,p除以q余数为r,p和q的最大公约数即q和r的最大公约数. java实现代码: public class ...

  8. AppClassLoader和WebAppClasssLoader的坑

    最近,打算学习一下spring mvc,为后续做一些积累. 搭建spring+mybatis,动态创建mapper,mapper的文件名称和类在一个目录,但是我之前犯个 错误,大小写写错了,结果我用普 ...

  9. Android——仿QQ聊天撒花特效

    实现这样的效果,你要知道贝塞尔曲线,何谓贝塞尔曲线?其实就是曲线,嘿嘿,关于曲线的概念大家可以去 Android绘图机制(二)——自定义View绘制形, 圆形, 三角形, 扇形, 椭圆, 曲线,文字和 ...

  10. JavaEE Tutorials (22) - 事务

    22.1Java EE应用中的事务35222.2什么是事务35322.3容器托管事务353 22.3.1事务属性354 22.3.2回滚容器托管事务357 22.3.3同步会话bean的实例变量357 ...