KVO是一种常见的设计模式,很多IOS程序员用它来作为装逼神器,常见的应用场景是配合MVC,当Model层数据发生改变的时候,View层可以通过实现定义好的观察者模式进行数据更新。下面通过一个简单例子:小孩和保姆 来说明KVO的各种坑。
直接上栗子:
1、Child.h
#import <Foundation/Foundation.h>
@interface Child : NSObject
@property (nonatomic) int age;
@property (nonatomic,strong) NSString *name;
- (id)initWithName:(NSString *)name andAge:(int)age;
@end
2、Child.m
#import "Child.h"
@implementation Child
- (id)initWithName:(NSString *)name andAge:(int)age{
self = [super init];
if (self) {
self.name = name;
self.age = age;
}
return self;
}
@end
3、Nuser.h
#import <Foundation/Foundation.h>
#import "Child.h"
@interface Nurse : NSObject
@property (nonatomic,strong) Child *child;
- (id)initWithChild:(Child *)child;
@end
4、Nuser.m
#import "Nurse.h"
@implementation Nurse
- (id)initWithChild:(Child *)child{
self = [super init];
if (self) {
self.child = child;
[self.child addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:@"监听名字"];
[self.child addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:@"监听年龄"];
}
return self;
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
NSLog(@"change=%@,content=%@",change,context);
if (object == self.child && [keyPath isEqualToString:@"name"]) {
NSLog(@"我检查到了名字");
}else if (object == self.child && [keyPath isEqualToString:@"age"]){
NSLog(@"我检查到了年龄");
}else{
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
- (void)dealloc{
[self.child removeObserver:self forKeyPath:@"name" context:@"监听名字"];
[self.child removeObserver:self forKeyPath:@"age" context:@"监听年龄"];
}
@end
mian.m
#import <Foundation/Foundation.h>
#import "Child.h"
#import "Nurse.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Child *ch = [[Child alloc] initWithName:@"小明" andAge:12];
Nurse *nu = [[Nurse alloc] initWithChild:ch];
//改变名字
[ch setName:@"我改名字了"];
//改变年龄
[ch setAge:100];
//KVC也能触发KVO
[ch setValue:@"KVC" forKey:@"name"];
}
return 0;
}
这个栗子保姆通过观察孩子的姓名和年龄,分别打印不同的内容。保姆是观察者,小孩是被观察者。Chiled的两个属性:name 和 age是观察的key。
坑1:因为保姆注册了对小孩的2个不同的观察者:name和age,所以在
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
方法中,要针对不同的object 和 keyPath进行不同处理,否则会漏掉某个key的观察
坑2:我们假设小孩类还有父类,并且父类也绑定了一些其他KVO,我们看到,上述回调函数体中只有一个判断,如果这个if不成立,这次KVO事件的触发就会到此中断了。但事实上,若当前类无法捕捉到这个KVO,那很有可能是在他的父类或父类的父类...因此在上述回调函数里面还要加这么一句:[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
坑3:相信大家都用过MJRefresh下拉刷新的控件吧,我记得当时开始用这个东西的时候经常crash掉,提示这样的信息,相信大家很熟悉这个错误:
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'An instance 0x100204980 of class Child was deallocated while key value observers were still registered with it.
当对象已经被release,如果此对象的观察者还没有移除的时候程序就crash了。