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

说完了前面一篇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下编译时会出错喽,暂时去掉吧。该段代码运行结果如下:

[email protected]: 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类的实现中加入以上类方法,重新编译运行:

[email protected]: 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;
}

编译运行结果如下:

[email protected]: 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)

时间: 2024-09-30 05:31:44

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

Rx 键值观察KVO的使用

键值观察KVO的使用 1,KVO 介绍 KVO(键值观察)是一种 Objective-C 的回调机制,全称为:key-value-observing. 该机制简单来说就是在某个对象注册监听者后,当被监听的对象发生改变时,对象会发送一个通知给监听者,以便监听者执行回调操作. 2,RxSwift 中的 KVO RxCocoa 提供了 2 个可观察序列 rx.observe 和 rx.observeWeakly,它们都是对 KVO 机制的封装,二者的区别如下. (1)性能比较 rx.observe 更

键值观察 KVO

Key-Value Observing Programming Guide 1,注册Key-Value Observing: 要实现这个目的,需要: 1)被观察的类对你想要观察的属性必须是服从Key-Value observing的 2)你必须注册被观察对象的观察对象,使用addObserver:forKeyPath:options:context:. 3)观察者类必须实现observeValueForKeyPath:ofObject:change:context: 重要提示:不是所有的类对所有

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

一,前言 Objective-C 中的键(key)-值(value)观察(KVO)并不是什么新鲜事物,它来源于设计模式中的观察者模式,其基本思想就是: 一个目标对象管理所有依赖于它的观察者对象,并在它自身的状态改变时主动通知观察者对象.这个主动通知通常是通过调用各观察者对象所提供的接口方法来实现的.观察者模式较完美地将目标对象与观察者对象解耦. 在 Objective-C 中有两种使用键值观察的方式:手动或自动,此外还支持注册依赖键(即一个键依赖于其他键,其他键的变化也会作用到该键).下面将一一

KVO键值观察简述

KVO 键值观察,简单来说就是为一个key添加一个观察者,当key的值发生改变的时候会发送通知,在接到通知的时候会有回调方法被调用 #import "ViewController.h" @interface ViewController (){     NSMutableDictionary * myDict; } @end @implementation ViewController - (IBAction)dasdas:(id)sender {          //改变key的值

KVO(键-值观察)

// 1.键-值观察 // 2.它提供一种机制,当指定的对象的属性被修改后,则对象就会接受到通知. // 3.符合KVC(Key-ValuedCoding)机制的对象才可以使用KVO // 4.实现过程 // ①注册,指定被观察者 // ②实现回调方法 // ③移除观察 - (void)viewDidLoad {     [super viewDidLoad];     // Do any additional setup after loading the view from its nib.

K-V-C 键值观察机制

在两个不同的控制器之间传值是iOS开发中常有的情况,应对这种情况呢,有多种的应对办法.kvc就是其中的一种,所以,我们就在此解释之.   key value observing  键值观察,给人一种高冷的感觉,其实,我们可以用一个通俗的例子来解释之.就拿美俄之间的间谍来举例子.美俄是两个各自独立的国家,但是为了各自的利益,彼此之间勾心斗角,不断的爆出间谍丑闻.打住!从政治的深渊回到技术层面O(∩_∩)O.美国想知道俄罗斯的最新的导弹技术,于是派间谍收集情报,(kvo的第一步:注册观察者-美国,监

深度理解Key-Value Observing 键值观察

前言   在上一阶段的开发过程中,我们大量使用了 KVO 机制,来确保页面信息的及时同步.也因此碰到了很多问题,促使我们去进一步学习 KVO 的相关机制,再到寻找更好的解决方案.鉴于 KVO 让人欲仙欲死的使用经历,在这里做一个简单分享.此分享的目的,更多的是在于点出 KVO 相关的技术点,供我们大家在学习和使用过程中做一个参考. 对于 KVO 的背后机制感兴趣的同学,可以直接看第三部分,KVC 和 isa-swizzling . 对于 替代方案感兴趣的同学,请直接跳到末尾的第五部分,有列出了目

iOS 键值观察(KVO)简述及实例理解

KVO概述: KVO,即:Key-Value Observing,直译为:基于键值的观察者.  它提供一种机制,当指定的对象的属性被修改后,则对象就会接受到通知. 简单的说就是每次指定的被观察的对象的属性被修改后,KVO就会自动通知相应的观察者了.KVO的优点: 当有属性改变,KVO会提供自动的消息通知.这样开发人员不需要自己去实现这样的方案:每次属性改变了就发送消息通知. 这是KVO机制提供的最大的优点.因为这个方案已经被明确定义,获得框架级支持,可以方便地采用. 开发人员不需要添加任何代码,

键-值观察

若想成为一个键的观察者,可添加如下代码. [theAppDelegate addObserver:self forKeyPath:@"fido" options:NSKeyValueObservingOptionOld context:nil]; 上述方法定义在NSObject中,实际上类似于说,“无论何时fido改变了就给我发个消息”,options和context决定fido改变时将哪些额外的数据与消息一起发送出去.触发方法过程如下 -(void)observeValueForKe