美文网首页KVO程序员
KVO基本使用及面试题分析

KVO基本使用及面试题分析

作者: coderLZ | 来源:发表于2018-03-21 10:12 被阅读1次

    基本概念

    KVO:Key-Value Observing,俗称’键值监听’,用于监听某个对象属性值的改变。

    基本使用

    自动触发

    创建Person对象,并创建属性name:

     Person *p = [[Person alloc]init];
      //添加观察者
      [p addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
    

    添加观察者后,当p的name属性的值发生变化时就会自动调用方法:

    -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
        NSLog(@"%@",change); 
    }
    

    注:要在dealloc方法中移除监听

    手动触发

    使用场景:当监听的属性改变时不需要自动触发,当需要时再去触发。
    解决方法是:先关闭自动触发,在Person.m中重写方法:

    //默认YES(自动)
    +(BOOL)automaticallyNotifiesObserversForKey:(NSString *)key{
        return NO;
    }
    

    然后在改变属性值的时候:

    [_P willChangeValueForKey:@"name"];
     _P.name  = [NSString stringWithFormat:@"%d",a++];
     [_P didChangeValueForKey:@"name"];
    

    这样就完成了手动触发。

    复杂情况

    场景:当Person中的属性是自定义Student 类的实例对象stu,在Student类中有属性 age,如何通过观察p对象来监听age属性?

    解决方法一

    在添加观察者时:

    [p addObserver:self forKeyPath:@"stu.age" options:NSKeyValueObservingOptionNew context:nil];
    
    

    弊端:当Student中有多个属性需要监听时,一个一个的添加监听者虽然没错但是太麻烦了!

    解决方法二

    在Person.m中重写该方法:

    //返回一个容器
    
    +(NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key{
    
        NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];
    
        if ([key isEqualToString:@"stu"]) {
    
            NSArray *addKeyPath = @[@"_stu.no",@"_stu.age"];
    
            keyPaths = [keyPaths setByAddingObjectsFromArray:addKeyPath];
    
        }
    
        return keyPaths;
    
    }
    

    这样只需要对stu监听即可。
    注:一定要带下划线

    内部实现原理

    面试题答案:当一个对象使用了KVO监听,iOS系统会修改这个对象的isa指针,使其指向通过Runtime动态创建的子类,该子类重写了set方法,内部实现会调用
    willChangeValueForKey、父类的setter、didChangeValueForKey。在didChangeValueForKey方法中又会调用监听器的监听方法。

    接下来一步步进行验证。
    首先我们利用运行时方法分别打印添加监听前后对象的类:

     Person *p = [[Person alloc]init];
     NSLog(@"添加监听之前%@",object_getClass(p));
      //添加观察者
     [p addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
     NSLog(@"添加监听之后%@",object_getClass(p));
    

    打印结果:

    KVODemo01[1749:181970] 添加监听之前Person
    KVODemo01[1749:181970] 添加监听之后NSKVONotifying_Person
    

    由此可知再添加监听后,系统创建了一个新的类,p对象的isa指向这个新的类。

    下一步我们可以打印添加监听前后的set方法的地址:

    NSLog(@"添加监听之前%p",[p methodForSelector:@selector(setName:)]);
    NSLog(@"添加监听之后%p",[p methodForSelector:@selector(setName:)]);
    

    打印结果:

    添加监听之前0x10570c3a0    
    添加监听之后0x105a56efe
    

    我们可以利用lldb分别看一下具体的方法实现:

    p (IMP)0x10570c3a0
    (IMP) $0 = 0x000000010570c3a0 (KVODemo01`-[Person setName:] at Person.h:12)
    
    p (IMP) 0x105a56efe
    (IMP) $2 = 0x0000000105a56efe (Foundation`_NSSetObjectValueAndNotify)
    

    由以上证明可知:添加了监听后,新创建的类重写了set方法,新创建的set方法内部调用了_NSSetObjectValueAndNotify.

    最后我们验证 _NSSetObjectValueAndNotify中是否调用了上面提到了函数和他们的执行顺序:
    在Person中重写:

    在Person中重写:
    -(void)willChangeValueForKey:(NSString *)key{
        NSLog(@"willChangeValueForKey-begin");
        [super willChangeValueForKey:key];
        NSLog(@"willChangeValueForKey-end");
    }
    -(void)didChangeValueForKey:(NSString *)key{
         NSLog(@"didChangeValueForKey-begin");
         [super didChangeValueForKey:key];
         NSLog(@"didChangeValueForKey-end");
        
    }
    

    打印结果:

    2018-03-20 16:06:30.610367+0800 KVODemo01[1892:206083] willChangeValueForKey-begin
    2018-03-20 16:06:30.610559+0800 KVODemo01[1892:206083] willChangeValueForKey-end
    2018-03-20 16:06:30.610661+0800 KVODemo01[1892:206083] didChangeValueForKey-begin
    2018-03-20 16:06:30.610924+0800 KVODemo01[1892:206083] {
        kind = 1;
        new = 0;
    }
    2018-03-20 16:06:30.611022+0800 KVODemo01[1892:206083] didChangeValueForKey-end
    

    由此可知,调用Foundation的_NSSetObjectValueAndNotify的方法后内部依次调用了

    willChangeValueForKey、
    父类的setter、
    didChangeValueForKey。
    在didChangeValueForKey方法中又会调用监听器的监听方法
    

    结语

    希望以上分享对读者有所帮助,更希望大神不吝赐教!

    相关文章

      网友评论

        本文标题:KVO基本使用及面试题分析

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