基于runtime机制
实现的。系统框架已经支持KVO,所以程序员在使用的时候非常简单。
1>注册指定Key路径的监听器
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context
相关参数:
observer:观察者,也就是KVO通知的订阅者。订阅着必须实现observeValueForKeyPath:ofObject:change:context:方法
keyPath:描述将要观察的属性,相对于被观察者。
options:KVO的一些属性配置;有四个选项。
options所包括的内容:
NSKeyValueObservingOptionNew:change字典包括改变后的值
NSKeyValueObservingOptionOld: change字典包括改变前的值
NSKeyValueObservingOptionInitial:注册后立刻触发KVO通知
NSKeyValueObservingOptionPrior:值改变前是否也要通知(这个key决定了是否在改变前改变后通知两次)
context: 上下文,这个会传递到订阅着的函数中,用来区分消息,所以应当是不同的。
注意:不要忘记解除注册,否则会导致资源泄露。
2>删除指定Key路径的监听器:
- (void)removeObserver:(NSObject *)anObserver forKeyPath:(NSString *)keyPath
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(void *)context
3>回调监听
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context;
keyPath:被监听的keyPath , 用来区分不同的KVO监听。
object: 被观察修改后的对象(可以通过object获得修改后的值)
change:保存信息改变的字典(可能有旧的值,新的值等)
context:上下文,用来区分不同的KVO监听
//change 字典中的old new 是关键字,专门用来存储新值和老值
NSLog(@"oldname %@",[change objectForKey:@"old"]); NSLog(@"new %@",[change objectForKey:@"new"]); }
注意,这里(NSString *)keyPath 传过来的就是你添加观察者的时候创建的key ,
如果想要监听多个属性,你可以根据整个值来判断到底是哪个值的变化触发了该方法
关于context
关于 context 参数,其作用可用来标识观察者的身份,在多个观察者观察同一键值时,
尤其在处理父类和子类都观察同一键值时非常有用。
那么如何正确声明一个 context 呢? 建议如下:
static void * XXContext = &XXContext;
其值就是一个存储自身指针的静态变量值,使用示例如下:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if (context == XXContext) {
} else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
设置类的A属性依赖于B、C属性时,对类的A属性进行观察,当B、C属性发生改变时,也会触发对A的观察者方法。
@interface Person : NSObject
@property(copy,nonatomic)NSString *name;
@property(copy,nonatomic)NSString *age;
@property(copy,nonatomic)NSString *home;
@end
.m中
Paste_Image.png
+ (NSSet *)keyPathsForValuesAffectingImageName
{
NSSet *set = [NSSet setWithObjects:@"age", @"sex", nil];
return set;
}
这样设置后通过改变 age、sex,都会触发对 name的观察者方法。
我们仔细看
NSKeyValueObservingOptionNew:change字典包括改变后的值
NSKeyValueObservingOptionOld: change字典包括改变前的值
会发现,上述的设置只是设置了返回的数值是改变前的还是改变后的,但是如果一直设置相同的值,不断重复,还是会不断的触发通知。如何做到值相同不再通知?
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
BOOL automatic = NO;
if ([key isEqualToString:@"age"]) {
automatic = NO;
} else {
automatic = [super automaticallyNotifiesObserversForKey:key];
}
return automatic;
}
- (void)setAge:(NSString *)age
{
if (self.age != age) {
//发送通知:键值即将改变
[self willChangeValueForKey:@"age"];
_age = age;
//发送通知:键值已经修改
[self didChangeValueForKey:@"age"];
}
}
这样设置后就可达到有新值通知,无新值不通知的效果。
- (void)viewDidLoad {
[super viewDidLoad];
Person *person = [[Person alloc]init];
[person addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:@"person"];
person.age = @"1111";
person.age = @"1111";
person.age = @"2222";
person.age = @"2222";
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
NSLog(@"+++ %@",[change objectForKey:@"new"]);
}
补充说明:
*******************************************
/*如果在这里设置了手动通知的话,就必须实现 下面这俩方法,在 set方法中,或者在 其他地方,
否则正常的 set方法后不会触发通知。
[self willChangeValueForKey:@"age"];
[self didChangeValueForKey:@"age"];
在ViewDidLoad中,如果person中实现了automaticallyNotifiesObserversForKey,
没有重写 set方法的话,下面的代码,“222”的不会触发观察方法, “DJ Earworm”会触发观察方法。
person.age = @"2222";
[person willChangeValueForKey:@"age"];
person.age = @"DJ Earworm";
[person didChangeValueForKey:@"age"];
p.age = 100; // 调用了set方法
p->_age = 998; // 不会监听到,因为KVO只监听通过set方法修改的属性值,
而p->age并未不是通过set方法修改属性值的,这种方式是通过修改全局变量 age,再把age赋值给 _age,最后达到修改属性值的效果。
****************************************************************
@interface Person : NSObject
{
@public //一定要写,不然会报错。
NSString *newage;
}
@property(copy,nonatomic)NSString *age;
@implementation Person
@synthesize age = newage;
Person *person = [[Person alloc]init];
person ->newage = @"nima";
NSLog(@"%@",person.age); zhey
这样一波操作,也可以达到修改属性值的目的。