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

时间:2022-12-10 09:19:20

原文链接:[原创]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:方法对特定对象的特定路径进行注册.

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

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

 #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 ;
}

简单说明下:老爸自然是要随时监控儿子的女朋友啊,可是这个老爸监视也就算了,还强行改变儿子的女朋友,这个就...随便想的一个例子,就当fun吧[原创]obj-c编程17:键值观察(KVO)。说明下主要方法:

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

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

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

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

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

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

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

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

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

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

 apple@kissAir: objc_src$./k

 -- ::26.757 k[:] My love girl is lili ... Does baba know?

 -- ::26.759 k[:] Yes , It's first time to induction son's girl_name!!!

 -- ::26.760 k[:] My son's girl_friend_name change from lili to mimi ... That's OK? :(

 -- ::26.760 k[:] My love girl is mimi ... Does baba know?

 -- ::26.761 k[:] My son's girl_friend_name change from libinbin to fanbinbin ... That's OK? :(

 -- ::26.761 k[:] My son's girl_friend_name change from mimi to libinbin ... That's OK? :(

 -- ::26.761 k[:] 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

 -- ::36.726 k[:] My love girl is lili ... Does baba know?

 -- ::36.729 k[:] Yes , It's first time to induction son's girl_name!!!

 -- ::36.729 k[:] My love girl is mimi ... Does baba know?

 -- ::36.730 k[:] 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 ;
}

编译运行结果如下:

apple@kissAir: objc_src$./k

-- ::59.284 k[:] My love girl is lili ... Does baba know?

-- ::59.286 k[:] Yes , It's first time to induction son's girl_name!!!

-- ::59.287 k[:] My son's girl_friend_name change from lili to 女学霸 ... That's OK? :(

-- ::59.287 k[:] My love girl is mimi ... Does baba know?

-- ::59.288 k[:] My son's girl_friend_name change from mimi to 女学霸 ... That's OK? :(

-- ::59.288 k[:] My love girl is libinbin ... Does baba know?

这回该老爸傻傻的以为儿子一直在和女学霸贪恋喽,各位童鞋当年也是这样糊弄老爸老妈的吧,嘿嘿。[原创]obj-c编程17:键值观察(KVO)

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

  1. obj-c编程17&colon;键值观察&lpar;KVO&rpar;

    说完了前面一篇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. 路径&lpar;keyPath&rpar;、键值编码(KVC)和键值观察(KVO)

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

  6. 09 &lpar;OC&rpar;&ast; 键路径&lpar;keyPath&rpar;、键值编码(KVC)、键值观察(KVO)

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

  7. &lbrack;原创&rsqb;obj-c编程16&colon;键值编码&lpar;KVC&rpar;

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

  8. &lbrack;深入浅出Cocoa&rsqb;详解键值观察(KVO)及其实现机理

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

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

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

随机推荐

  1. Android中AIDL的理解与使用&lpar;二&rpar;——跨应用绑定Service并通信

    跨应用绑定Service并通信: 1.(StartServiceFromAnotherApp)AIDL文件中新增接口: void setData(String data); AppService文件中 ...

  2. PopupWindow响应返回键的问题

    假设情景是这样的:在一个Activity中弹出一个PopupWindow,要求在按返回键时关闭该PopupWindow. 如果该PopupWindow是无焦点的(默认情况),那么可以在Activity ...

  3. POJ -3190 Stall Reservations (贪心&plus;优先队列)

    http://poj.org/problem?id=3190 有n头挑剔的奶牛,只会在一个精确时间挤奶,而一头奶牛需要占用一个畜栏,并且不会和其他奶牛分享,每头奶牛都会有一个开始时间和结束时间,问至少 ...

  4. 自定义web服务器&lpar;四&rpar;

    关于HTTP协议的具体内容,前面章节已经有所讲解,相信读者已有所了解,在此不在累述,本章节讲解自定义web服务器.  一,.net提供自定义Web服务器的类 以下只是写主要的类 1.HTTPListe ...

  5. android发送&sol;接收Json包含中文的处理

    转自:http://wiki.neal365.com/2013/02/25/android%E5%8F%91%E9%80%81%E6%8E%A5%E6%94%B6json%E5%8C%85%E5%90 ...

  6. strchr&comma;wcschr 及strrchr&comma; wcsrchr&comma;&lowbar;tcschr&comma;&lowbar;tcsrchr函数

           strchr,wcschr 及strrchr, wcsrchr,_tcschr,_tcsrchr函数 (1) char *strchr( const char *string, int ...

  7. Ionic 2 中创建一个照片倾斜浏览组件

    内容简介 今天介绍一个新的UI元素,就是当我们改变设备的方向时,我们可以看到照片的不同部分,有一种身临其境的感觉,类似于360全景视图在移动设备上的应用. 倾斜照片浏览 Ionic 2 实例开发 新增 ...

  8. RabbitMQ简单应用の公平分发(fair dipatch)

    公平分发(fair dipatch)和轮询分发其实基本一致,只是每次分发的机制变了,由原来的平均分配到现在每次只处理一条消息 1.MQ连接工厂类Connection package com.mmr.r ...

  9. C&num;窗体如何通过keybd&lowbar;event&lpar;&rpar;函数模拟键盘按键(组合键)产生事件

    如何模拟键盘按键触发产生的事件,比如模拟按下Alt + F4 关闭当前程序,Ctrl+Shift 切换输入法等 可以通过win32api 键盘事件 keybd_event() 来实现 1.定义键盘按键 ...

  10. 【Nginx】ngx&lowbar;event&lowbar;core&lowbar;module模块

    ngx_event_core_module模块属于事件模块,它是其他事件类模块的基础.它主要完毕下面任务: 创建连接池 决定使用哪些事件驱动机制 初始化将要使用的事件模块 以下分析该模块的代码. ng ...