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

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

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

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

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

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

#import <Foundation/Foundation.h>

#define msg(...) NSLog(__VA_ARGS__)
#define mki(x) [NSNumber numberWithInt:x]

@interface Son:NSObject{
	NSString *girl_friend_name;
}
	@property NSString *girl_friend_name;
	-(void)think;
	-(void)fall_in_love_with:(NSString *)girl_name;
@end

@implementation Son
	@synthesize girl_friend_name;

	-(void)fall_in_love_with:(NSString *)girl_name{
		self.girl_friend_name = girl_name;
	}

	-(void)think{
		msg(@"My love girl is %@ ... Does baba know?",girl_friend_name);
	}
@end

@interface Baba:NSObject{
	Son *son;
}
	@property Son *son;
@end

@implementation Baba
	@synthesize son;

	-(id)initWithSon:(Son *)son_v{
		self = [super init];
		if(self){
			son = son_v;
			[son addObserver:self forKeyPath:@"girl_friend_name" \
				options:(NSKeyValueObservingOptionNew|\
				NSKeyValueObservingOptionOld|NSKeyValueObservingOptionInitial) \
				context:nil];
		}
		return self;
	}

	-(void)observeValueForKeyPath:(NSString *)key_path \
		ofObject:(id)obj change:(NSDictionary *)change \
		context:(void *)context{

			NSString *old_name = [change objectForKey:NSKeyValueChangeOldKey];
			NSString *new_name = [change objectForKey:NSKeyValueChangeNewKey];

			if(!old_name){
				//NSKeyValueObservingOptionInitial选项的作用,第一次hook会发一次消息
				//以后每次更改会发一次消息。
				msg(@"Yes , It's first time to induction son's girl_name!!!");
			}else{
				if([new_name isEqualToString:@"libinbin"]){
					[obj fall_in_love_with:@"fanbinbin"];
				}
				msg(@"My son's %@ change from %@ to %@ ... That's OK? :(",\
				key_path,old_name,new_name);
			}
	}

	-(void)dealloc{
		[son removeObserver:self forKeyPath:@"girl_friend_name"];
		son = nil;
		//[super dealloc];
	}
@end

int main(int argc,char *argv[])
{
	@autoreleasepool{
		Son *son = [[Son alloc] init];
		[son fall_in_love_with:@"lili"];
		[son think];

		Baba *baba = [[Baba alloc] initWithSon:son];
		[son fall_in_love_with:@"mimi"];
		[son think];

		//儿子本来喜欢的是libinbin,可是...
		[son fall_in_love_with:@"libinbin"];
		//被他老爸用特异功能改成fanbinbin啦!
		[son think];
	}
	return 0;
}

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

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

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

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

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

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

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

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

apple@kissAir: objc_src$./k

2014-07-06 20:11:26.757 k[2109:507] My love girl is lili ... Does baba know?

2014-07-06 20:11:26.759 k[2109:507] Yes , It's first time to induction son's girl_name!!!

2014-07-06 20:11:26.760 k[2109:507] My son's girl_friend_name change from lili to mimi ... That's OK? :(

2014-07-06 20:11:26.760 k[2109:507] My love girl is mimi ... Does baba know?

2014-07-06 20:11:26.761 k[2109:507] My son's girl_friend_name change from libinbin to fanbinbin ... That's OK? :(

2014-07-06 20:11:26.761 k[2109:507] My son's girl_friend_name change from mimi to libinbin ... That's OK? :(

2014-07-06 20:11:26.761 k[2109:507] My love girl is fanbinbin ... Does baba know?

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

+(BOOL)automaticallyNotifiesObserversForKey:(NSString *)key{
		if([key isEqualToString:@"girl_friend_name"])
			return NO;
		return YES;
	}

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

apple@kissAir: objc_src$./k

2014-07-06 20:37:36.726 k[2180:507] My love girl is lili ... Does baba know?

2014-07-06 20:37:36.729 k[2180:507] Yes , It's first time to induction son's girl_name!!!

2014-07-06 20:37:36.729 k[2180:507] My love girl is mimi ... Does baba know?

2014-07-06 20:37:36.730 k[2180:507] My love girl is libinbin ... Does baba know?

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

#import <Foundation/Foundation.h>

#define msg(...) NSLog(__VA_ARGS__)
#define mki(x) [NSNumber numberWithInt:x]

@interface Son:NSObject{
	NSString *girl_friend_name;
}
	//在属性默认为atomic的情况下,如果自定义写者方法必须同时自定义读者方法,
	//或者将属性改为nonatomic模式。
	@property(nonatomic) NSString *girl_friend_name;
	-(void)think;
	-(void)fall_in_love_with:(NSString *)girl_name;
@end

@implementation Son
	@synthesize girl_friend_name;

	-(void)fall_in_love_with:(NSString *)girl_name{
		self.girl_friend_name = girl_name;
	}

	-(void)think{
		msg(@"My love girl is %@ ... Does baba know?",girl_friend_name);
	}

	+(BOOL)automaticallyNotifiesObserversForKey:(NSString *)key{
		if([key isEqualToString:@"girl_friend_name"])
			return NO;
		return YES;
	}

	-(void)setGirl_friend_name:(NSString *)name{
		[self willChangeValueForKey:@"girl_friend_name"];
		//让老爸以为自己和女学霸谈恋爱哦
		girl_friend_name = @"女学霸";
		[self didChangeValueForKey:@"girl_friend_name"];
		//恢复本色吧,BAD BOY... :)
		girl_friend_name = name;
	}
@end

@interface Baba:NSObject{
	Son *son;
}
	@property Son *son;
@end

@implementation Baba
	@synthesize son;

	-(id)initWithSon:(Son *)son_v{
		self = [super init];
		if(self){
			son = son_v;
			[son addObserver:self forKeyPath:@"girl_friend_name" \
				options:(NSKeyValueObservingOptionNew|\
				NSKeyValueObservingOptionOld|NSKeyValueObservingOptionInitial) \
				context:nil];
		}
		return self;
	}

	-(void)observeValueForKeyPath:(NSString *)key_path \
		ofObject:(id)obj change:(NSDictionary *)change \
		context:(void *)context{

			NSString *old_name = [change objectForKey:NSKeyValueChangeOldKey];
			NSString *new_name = [change objectForKey:NSKeyValueChangeNewKey];

			if(!old_name){
				//NSKeyValueObservingOptionInitial选项的作用,第一次hook会发一次消息
				//以后每次更改会发一次消息。
				msg(@"Yes , It's first time to induction son's girl_name!!!");
			}else{
				if([new_name isEqualToString:@"libinbin"]){
					[obj fall_in_love_with:@"fanbinbin"];
				}
				msg(@"My son's %@ change from %@ to %@ ... That's OK? :(",\
				key_path,old_name,new_name);
			}
	}

	-(void)dealloc{
		[son removeObserver:self forKeyPath:@"girl_friend_name"];
		son = nil;
		//[super dealloc];
	}
@end

int main(int argc,char *argv[])
{
	@autoreleasepool{
		Son *son = [[Son alloc] init];
		[son fall_in_love_with:@"lili"];
		[son think];

		Baba *baba = [[Baba alloc] initWithSon:son];
		[son fall_in_love_with:@"mimi"];
		[son think];

		//儿子本来喜欢的是libinbin,可是...
		[son fall_in_love_with:@"libinbin"];
		//被他老爸用特异功能改成fanbinbin啦!
		[son think];
	}
	return 0;
}

编译运行结果如下:

apple@kissAir: objc_src$./k

2014-07-06 20:58:59.284 k[2270:507] My love girl is lili ... Does baba know?

2014-07-06 20:58:59.286 k[2270:507] Yes , It's first time to induction son's girl_name!!!

2014-07-06 20:58:59.287 k[2270:507] My son's girl_friend_name change from lili to 女学霸 ... That's OK? :(

2014-07-06 20:58:59.287 k[2270:507] My love girl is mimi ... Does baba know?

2014-07-06 20:58:59.288 k[2270:507] My son's girl_friend_name change from mimi to 女学霸 ... That's OK? :(

2014-07-06 20:58:59.288 k[2270:507] My love girl is libinbin ... Does baba know?

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

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

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

    原文链接:[原创]obj-c编程17:键值观察(KVO) 系列专栏链接:objective-c 编程系列 说完了前面一篇KVC,不能不说说它的应用KVO(Key-Value Observing)喽.K ...

  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. [深入浅出Cocoa]详解键值观察(KVO)及其实现机理

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

  8. K-V-O 键值观察机制

    在两个不同的控制器之间传值是iOS开发中常有的情况,应对这种情况呢,有多种的应对办法.kvc就是其中的一种,所以,我们就在此解释之.   key value observing  键值观察,给人一种高 ...

  9. iOS - KVO 键值观察

    1.KVO KVO 是 Key-Value Observing 的简写,是键值观察的意思,属于 runtime 方法.Key Value Observing 顾名思义就是一种 observer 模式用 ...

随机推荐

  1. Objc生成搜索引擎查询字符串

    大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请多提意见,如果觉得不错请多多支持点赞.谢谢! hopy ;) 拿baidu为例,百度的搜索url为: http://www. ...

  2. 每个程序员都应该用MBP

    换笔记本的想法很久了,前段时间换工作就想看换工作之后是什么情况吧.可能工作配的笔记本就是MBP.后来发现是想多了,新工作的笔记本是Thinkpad X240, 配置完全够用了,8G内存+128G的FL ...

  3. [django]项目打包构建

    django项目的结构大体上都是类似,打包主要的功能就是把一些不需要部署的文件剔除,把需要部署的文件直接压缩打包. 这里还想集成一个配置文件模板生成配置文件的过程,或者写一个配置文件生成的工具,不用每 ...

  4. Qt中实现启动画面

    纵所周之,当一个程序的启动比较耗时的时候,为了不让用户枯燥的等待或者是误以为程序运行异常了,所以我们都会在启动比较耗时的程序中加上启动界面 ,例如office软件等等. 在Qt中实现启动界面,主要就是 ...

  5. [GitHub]第四讲:合并分支

    本地两个分支合并 先从最简单的一种情况着手.现在项目只有一个 master 分支,我来新建一个 idea 分支,实现自己的想法,commit 一下.那现在仓库内的情况就是这样的 这个是前面已经见过的情 ...

  6. B2B、B2C、B2D的简单理解

    B2D现在非常流行,顾名思义,B2D 就是指那些以开发者为对象的服务,它们通过 API 等形式"售卖"自己某一方面的特长.B2D(Business to Developer)市场很 ...

  7. Install and run DB Query Analyzer 6.04 on Microsoft Windows 10

          Install and run DB Query Analyzer 6.04 on Microsoft Windows 10  DB Query Analyzer is presented ...

  8. catalina.sh设置JAVA_HOME后还无法解决更换JDK有关问题

    catalina.sh设置JAVA_HOME后还无法解决更换JDK问题 表示linux已经安装默认的JDK,需要查找配置文件,更换JDK路径为指定的路径 在root用户下 使用echo $PATH 查 ...

  9. WIP完工入库及完工退回的几个重要问题

    1.必须向CST_COMP_SNAP_INTERFACE表中插入此工单所有工序的数据(也就是说同样的工单插入多条,只是工序号不一样) 标准文档: Note: If there are multiple ...

  10. pandas小记:pandas基本设置

    http://blog.csdn.net/pipisorry/article/details/49519545 ): print(df) Note: 试了好久终于找到了这种设置方法! 它是这样实现的 ...