KVO/KVC

什么是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 *)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)