0、KVO(键值监听)

2019-06-21  本文已影响0人  ForstDragon

KVO简介

KVO全称为(Key-Value-Observing),俗称兼职兼听,用于监听摸个对象属性值的改变,由于KVO的实现机制,所以对属性才会发生作用,一般继承自NSObject的对象都默认支持KVO。
常用的操作方法有:

//注册指定路径的监听器: 观察者可以接收keyPath属性的变化事件。
addObserver: forKeyPath: 属性名  options : context:nil

//监听回调:当keyPath属性发生改变后,KVO会回调这个方法来通知观察者。
observeValueForKeyPath:  ofObject:  change:  context:


//删除指定key路径的监听
//会在dealloc移除监听,需要注意的是,调用removeObserver需要在观察者消失之前,否则会导致Crash。
removeObserver: forKeyPath、removeObserver: forKeyPath: context:

未被KVO监听的对象,改变时会直接调用set,get方法


未被KVO监听的对象.png

添加了KVO的监听属性改变,isa指针会只想runtime生成的一个派生类NSKVONotifying_Person,NSKVONotifying_person是Person类的子类,会调用Foundation框架里面的_NSSetIntValueAndNotify


添加了KVO监听的对象.png

1、KVO的使用

Person类中有一个age属性.

@interface Person : NSObject

@property (nonatomic ,assign) int age;

@end

Controller中监听Person对象age属性的改变, 实现observeValueForKeyPath方法.

- (void)viewDidLoad {
    [super viewDidLoad];

    Person *p1 = [[Person alloc] init];
    [p1 addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:nil];
    p1.age = 10; 
}

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
    NSLog(@"监听到%@的%@属性改变:%@", object, keyPath, change);
}

-(void)dealloc{
    [Person removeObserver:self forKeyPath:@"age"];
}

当对p1的age属性赋值时, 会调用observeValueForKeyPath方法, 监听到age属性的改变.

2、KVO的本质

- (void)viewDidLoad {
    [super viewDidLoad];

    Person *p1 = [[Person alloc] init];
    Person *p2 = [[Person alloc] init];

    [p1 addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:nil];

    p1.age = 10;
    p2.age = 10;

    NSLog(@"%@--%@", p1, p2);
}

我们来研究一下, 为p1添加监听以后, p1的isa指针是否发生了变化?


isa变化.png

1,可以发现, 为p1添加监听以后p1isa指针指向了一个全新的类NSKVONotifying_Person,p2isa指针还是指向Person.
2,注意这里, 如果使用[p1 class]获取p1的类型是不准确的, 后续会有解释
p1isa指针发生了改变, 当调用p对象的age属性赋值时, 其本质是调用的哪个方法呢?

- (void)viewDidLoad {
    [super viewDidLoad];

    Person *p1 = [[Person alloc] init];
    Person *p2 = [[Person alloc] init];

    NSLog(@"p1添加KVO监听之前 - %p %p",
          [p1 methodForSelector:@selector(setAge:)],
          [p2 methodForSelector:@selector(setAge:)]);

    [p1 addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:nil];

    NSLog(@"p1添加KVO监听之后 - %p %p",
          [p1 methodForSelector:@selector(setAge:)],
          [p2 methodForSelector:@selector(setAge:)]);

    p1.age = 10;
    p2.age = 10;

    NSLog(@"%@--%@", p1, p2);
}
方法调用.png
通过对比查看为p1添加监听以后调用的方法, p1添加监听之前调用的setAge方法, 而添加监听以后, 调用的是Foundation中的_NSSetIntValueAndNotify方法.

NSKVONotifying_PersonPerson的一个子类, 在NSKVONotifying_Person内部也有setAge方法.
添加监听以后, 对象的isa指针发生变化. isa指向的类对象发生变化, 生成一个全新的类, 继承自原来的类.
-set方法发生变化, 调用一个C语言的私有函数, 在私有函数中会调用 willChangeValueForKey, set方法, didChangeValueForKey.

3、查看中间类的内容.

@implementation NSKVONotifying_Person1

-(void)setAge:(int)age{
    _NSSetIntValueAndNotify();
}
void _NSSetIntValueAndNotify(){
    [self willChangeValueForKey:age];
    [super setAge:age];
    [self didChangeValueForKey:age];
}

-(void)didChangeValueForKey:(NSString *)key{
    [observer observeValueForKeyPath:@"age" ofObject:self change:@{} context:nil];
}

- (Class)class
{
    return class_getSuperclass(object_getClass(self));
}

@end

如何手动触发KVO?
,通过中间类的实现可以知道,必须手动调用willChange和didChange方法,然后才会触发监听的回调

中间类的具体实现.png
上一篇 下一篇

猜你喜欢

热点阅读