KVO

浪尽此生 提交于 2020-02-02 17:32:46

通知和代理:
通知:一对多(随处可发通知,随处可以接收通知)
优点:发送者和接受者都不需要知道对方是谁
缺点:发送方没有办法接受到反馈值

代理:一对一(一触发通知,即刻接收)
优点:支持的类有详尽的信息
缺点:必须支持委托

KVO

概述

Key-Value Observing,键值观察,观察者模式的衍生
对目标对象的某属性添加观察,当属性发生变化时,通过触发观察者对象实现的接口方法,自动通知观察者
较完美的将目标对象和观察者对象进行解耦

KVO的定义是对NSObject的扩展所实现,所以,继承自NSObject的类,都能使用KVO

过程

  1. 注册观察者
  2. 监听回调
  3. 移出监听
  • 注册
- (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决定了是否在改变前改变后通知两次)
  • 回调
  1. 调用方法
- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context;
  1. 自动
//通过属性的点语法间接调用
objc.name = @"";

// 直接调用set方法
[objc setName:@"Savings"];
 
// 使用KVC的setValue:forKey:方法
[objc setValue:@"Savings" forKey:@"name"];
 
// 使用KVC的setValue:forKeyPath:方法
[objc setValue:@"Savings" forKeyPath:@"account.name"];
  1. 手动
第一步.重写方法,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
在这里插入图片描述
中间类的内部实现:

- (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的指向)
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!