KVO
通知和代理:
通知:一对多(随处可发通知,随处可以接收通知)
优点:发送者和接受者都不需要知道对方是谁
缺点:发送方没有办法接受到反馈值
代理:一对一(一触发通知,即刻接收)
优点:支持的类有详尽的信息
缺点:必须支持委托
KVO
概述
Key-Value Observing,键值观察,观察者模式的衍生
对目标对象的某属性添加观察,当属性发生变化时,通过触发观察者对象实现的接口方法,自动通知观察者
较完美的将目标对象和观察者对象进行解耦
KVO的定义是对NSObject的扩展所实现,所以,继承自NSObject的类,都能使用KVO
过程
- 注册观察者
- 监听回调
- 移出监听
- 注册
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
observer:观察者
keyPath:所观察的属性
options:属性配置
context:上下文
options
NSKeyValueObservingOptionNew:change字典包括改变后的值
NSKeyValueObservingOptionOld:change字典包括改变前的值
NSKeyValueObservingOptionInitial:注册后立刻触发KVO通知
NSKeyValueObservingOptionPrior:值改变前是否也要通知(这个key决定了是否在改变前改变后通知两次)
- 回调
- 调用方法
- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context;
- 自动
//通过属性的点语法间接调用
objc.name = @"";
// 直接调用set方法
[objc setName:@"Savings"];
// 使用KVC的setValue:forKey:方法
[objc setValue:@"Savings" forKey:@"name"];
// 使用KVC的setValue:forKeyPath:方法
[objc setValue:@"Savings" forKeyPath:@"account.name"];
- 手动
第一步.重写方法,YES可以调用,NO不可以
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)theKey {
BOOL automatic = NO;
if ([theKey isEqualToString:@"name"]) {
automatic = NO;//对该key禁用系统“自动通知”,
//若要直接禁用该类的KVO则直接返回NO;
}
//对其他非手动实现的key,转交super处理
automatic = [super automaticallyNotifiesObserversForKey:theKey];
return automatic;
}
第二步.重写setter方法
- (void)setName:(NSString *)name {
if (name != _name) {
[self willChangeValueForKey:@"name"];
_name = name;
[self didChangeValueForKey:@"name"];
//在操作前后分别调用will和did方法
//用于通知系统该key的属性值即将和已经发生变化
}
}
- 移除
- (void)dealloc{
[self removeObserver:self forKeyPath:@"age"];
}
崩溃原因:
1.观察者未实现监听
2.未及时移除见监听
3.多次移除监听
键值观察依赖键:
一个属性的值依赖另一个对象中的一个多个属性。
即一个属性发生变更,被依赖的属性值,也应当为其变更进行标记
#import "TargetWrapper.h"
- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
if ([keyPath isEqualToString:@"age"]){
Class classInfo = (Class)context;
NSString * className = [NSString stringWithCString:object_getClassName(classInfo) encoding:NSUTF8StringEncoding];
NSLog(@" >> class: %@, Age changed", className);
NSLog(@" old age is %@", [change objectForKey:@"old"]);
NSLog(@" new age is %@", [change objectForKey:@"new"]);
}else if ([keyPath isEqualToString:@"information"]){
Class classInfo = (Class)context;
NSString * className = [NSString stringWithCString:object_getClassName(classInfo) encoding:NSUTF8StringEncoding];
NSLog(@" >> class: %@, Information changed", className);
NSLog(@" old information is %@", [change objectForKey:@"old"]);
NSLog(@" new information is %@", [change objectForKey:@"new"]);
}else{
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
与RunLoop之间的关系
与线程之间的关系:
KVO行为是“同步”的,发生与所观察的值发生变化的同样的线程上。
没有RunLoop的处理
如果使用KVO来改变其他线程的属性值,应格外小心,除非能确定所有的观察者都使用线程安全的方法
所以,使用多个队列和线程,不应该在它们之间使用KVO
中间类
实现原理:
中间类!!:是原类的子类,并将当前对象的isa指针指向这个中间类
添加KVO之后,person的isa指向 NSKVONotifying_Person 类对象
setAge的实现也进行类改变,调用的是Foundation 中 _NSSetLongLongValueAndNotify 方法
中间类的命名规则为:NSKVONotifying_xxx
image
中间类的内部实现:
- (void)setAge:(int)age{
_NSSetLongLongValueAndNotify();//这个方法调用顺序,何处调用,都在setter方法改变中详解
}
重写class方法
如果没有重写此方法,则当对象调用class方法时,
会在自己的方法缓存列表,方法列表,父类缓存,方法列表一直向上去查找该方法
因为class方法是NSObject中的方法,如果不重写最终可能会返回NSKVONotifying_Person
就会将该类暴露出来,也给开发者造成困扰
- (Class)class {
return [LDPerson class];
}
- (void)dealloc {
// 收尾工作
}
这个方法可以当做使用了KVO的一个标记,系统可能也是这么用的。
如果我们想判断当前类是否是KVO动态生成的类,就可以从方法列表中搜索这个方法
- (BOOL)_isKVOA {
return YES;
}
setter方法详解:
利用父类person分析
- (void)setAge:(int)age{
_age = age;
NSLog(@"setAge:");
}
- (void)willChangeValueForKey:(NSString *)key{
[super willChangeValueForKey:key];
NSLog(@"willChangeValueForKey");
}
- (void)didChangeValueForKey:(NSString *)key{
NSLog(@"didChangeValueForKey - begin");
[super didChangeValueForKey:key];
NSLog(@"didChangeValueForKey - end");
}
@end
//打印结果
KVO-test[1457:637227] willChangeValueForKey
KVO-test[1457:637227] setAge:
KVO-test[1457:637227] didChangeValueForKey - begin
KVO-test[1457:637227] didChangeValueForKey - end
KVO-test[1457:637227] willChangeValueForKey
KVO-test[1457:637227] didChangeValueForKey - begin
KVO-test[1457:637227] didChangeValueForKey - end
首先调用willChangeValueForKey:方法
然后调用setAge:方法
最后调用didChangeValueForKey:方法
其中,调用[super didChangeValueForKey:key];通知属性值已经改变,然后监听自己的方法
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
这里记一个小点:
NSLog(@"%@",[objA class]);//打印:ObjectA
NSLog(@"%@",object_getClass(objA));//打印:NSKVONotifying_ObjectA(返回isa的指向)