美文网首页
0、KVO(键值监听)

0、KVO(键值监听)

作者: ForstDragon | 来源:发表于2019-06-21 11:21 被阅读0次

    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

    相关文章

      网友评论

          本文标题:0、KVO(键值监听)

          本文链接:https://www.haomeiwen.com/subject/neuzfctx.html