说完了前面一篇KVC,不能不说说它的应用KVO(Key-Value Observing)喽。KVO类似于ruby里的hook功能,就是当一个对象属性发生变化时,观察者可以跟踪变化,进而观察或是修正这个变化,这是通过回调观察者注册的回调函数来完成的。要使用键值观察,必须满足3个条件:
1 被观察对象必须对所观察属性使用符合KVC标准的存取器方法;
2 观察者必须实现接受通知的方法(回调方法):-observeva lue:forKeyPath:ofObject:change:context:,该方法在值发生变化时被调用,并且可以定制行为:比如同时接收新值和原值以及其他自定义信息;
3 观察者通过-addObserver:forKeyPath:options:context:方法对特定对象的特定路径进行注册.
还要注意的是,在观察者完成对被观察者的观察后,必须将自己移除,否则当观察者释放后,来自被观察者的变化通知消息将无处可发,可能会导致引用崩溃。另外可以使用多个观察者对同一个对象的同一个路径进行观察啊,类似于形成一个观察链。
下面我将会写一个简单的例子,用代码说明上述内容:
#import
#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)observeva lueForKeyPath:(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标志的含义参见代码注释吧。
-observeva lueForKeyPath回调方法中的change是一个字典参数,其中包括:

其中NSKeyValueChangeKindKey指定了接收到得变化类型,可能有以下几种值:<??http://www.2cto.com/kf/ware/vc/" target="_blank" class="keylink">vcD4KPHA+PGltZyBzcmM9"https://www.cppentry.com/upload_files/article/45/1_c0ra9__.png" alt="\">
书上的代码在该方法定义的末尾部分增加了其对父类中同名方法的回溯调用:
[super observeva lueForkeyPath: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[