KVO/KVC

作者: 徐老茂 | 来源:发表于2018-12-17 14:07 被阅读21次

    什么是KVO

    • KVO是Key-Value Observing的首字母缩写
    • KVO是Object-C对观察者设计模式的实现
    • Apple使用了isa混写(isa-swizzling)来实现KVO

    KVO 提供一种机制,指定一个被观察对象(例如 A 类),当对象某个属性(例如 A 中的字符串 name)发生更改时,对象会获得通知,并作出相应处理;【且不需要给被观察的对象添加任何额外代码,就能使用 KVO 机制】
    用一张图来描述一下KVO的实现机制

    KVO
    上图可以看出,注册一个对象的观察者的时候,实际上是调用了系统的- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;这个方法,调用这个方法后观察者观察对象A中的某个属性,然后系统会在运行时动态的创建一个NSKVONotifying_A的这么样一个类,原来的对象A的isa指针重新指向了NSKVONotifying_A这个类,把isa的指向进行修改就是isa混写技术.NSKVONotifying_A是类A的子类,并重写了其中的Setter方法,通过对Setter方法的重写达到可以通知所有观察者的目的.
    接下来,在XCode工程当中,来实际通过Setter方法的设置,KVO的监听来感受一下KVO的实现.
    创建两个文件,MyObject和MyObserver.
    • MyObject
    @interface MyObject : NSObject
    @property (nonatomic,assign) int value;
    -(void)increase;
    @end
    @implementation MyObject
    
    -(instancetype)init
    {
        self = [super init];
        if (self) {
            _value = 0;
        }
        return self;
    }
    
    -(void)increase
    {
        _value += 1;
    }
    
    @end
    
    • MyObserver
    @implementation MyObserver
    -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
    {
        NSNumber *valueNum = [change valueForKey:NSKeyValueChangeNewKey];
        NSLog(@"value is %@",valueNum);
    }
    @end
    

    然后在AppDelegate中进行KVO的监听

    • AppDelegate
    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
        // Override point for customization after application launch.
        
        MyObject *obj = [[MyObject alloc]init];
        MyObserver *observer = [[MyObserver alloc]init];
        
        //调用KVO方法监听obj的value属性的变化
        [obj addObserver:observer forKeyPath:@"value" options:NSKeyValueObservingOptionNew context:NULL];
        obj.value = 1;
        return YES;
    }
    

    可以看到控制台打印出了结果


    说明监听成功了.
    在obj.value那里打个断点,看看MyObject是怎么被改写的.


    不出所料,在监听的属性Value被改写后,MyObject变成了NSKVONotifying_MyObject了.
    为什么调用Setter方法就可以实现这种KVO的监听呢.

    重写的Setter添加的方法
    • -(void)willChangeValueForKey:(NSString *)key
    • -(void)didChangeValueForKey:(NSString *)key

    那么在NSKVONotifying_MyObject中的Setter方法就变成了下面这样

    -(void)setValue:(id)obj
    {
        [self willChangeValueForKey:@"keyPath"];
        //调用父类,也就是原类的实现
        [super setValue:obj];
        [self didChangeValueForKey:@"keyPath"];
    }
    

    接下来有两个问题.

    1.通过KVC设置Value能否生效

    这个问题用代码来验证一下就可以了,如下所示

    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
        MyObject *obj = [[MyObject alloc]init];
        MyObserver *observer = [[MyObserver alloc]init];
        //调用KVO方法监听obj的value属性的变化
        [obj addObserver:observer forKeyPath:@"value" options:NSKeyValueObservingOptionNew context:NULL];
        obj.value = 1;
        // 使用kvc来改变value的值
        [obj setValue:@2 forKey:@"value"];
        return YES;
    }
    

    结果控制台打印如下:



    说明使用KVC设置属性的方式是可以出发KVO的,说明KVC设置属性是触发了Setter方法

    2.使用成员变量赋值会出发KVO吗

    我们在AppDelegate调用obj的increase方法,发现控制台只打印了value is 1,说明对成员变量赋值不会触发KVO,但对increate方法进行以下操作就不一样了.
    不过如果我们把increase方法变成下面这样,再运行试试

    -(void)increase
    {
        [self willChangeValueForKey:@"value"];
        _value += 1;
        [self didChangeValueForKey:@"value"];
    }
    

    发现又触发了KVO
    所以根据上面的实验总结出下面几点

    • 使用setter方法改变值KVO才会生效
    • 使用setValue:forKey:改变KVO才会生效
    • 成员变量直接修改需手动添加KVO才会生效

    KVC

    KVC是Key-Value coding的缩写,也就是键值编码,和键值编码相关的两个方法就是下面这两个

    • -(id)valueForKey:(NSString *)key
      这个可以调用某个实例的ValueForKey:方法,来获取和key同名或相似名称的实例变量的值
    • -(void)setValue forKey:(NSString *)key
      根据这个方法可以设置某一个对象和这个key同名或者相似名称的实例变量的值.

    相关文章

      网友评论

        本文标题:KVO/KVC

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