IOS KVO原理解析与应用

一、KVO概述

KVO,即:Key-Value Observing,是Objective-C对观察者模式的实现,每次当被观察对象的某个属性值发生改变时,注册的观察者便能获得通知,这种模式有利于两个类间的解耦合,尤其是对于业务逻辑与视图控制 这两个功能的解耦合。

二、KVO有哪些应用?

  • NSOperation
  • NSOperationQueue
  • RAC

三、KVO的使用和实现?

1、使用KVO

1.注册观察者,指定被观察对象的属性:

[_people addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:nil];
复制代码

2.在观察者中实现以下回调方法:

- (void)observeValueForKeyPath:(NSString *)keyPath  
                      ofObject:(id)object  
                        change:(NSDictionary *)change  
                       context:(void *)context  
   
{  
        NSString *name = [object valueForKey:@"name"]; 
        NSLog(@"new name is: %@", name);  
}  
复制代码

只要People对象中的name属性发生变化,系统会自动调用该方法。

3.最后不要忘了在dealloc中移除观察者

[_people removeObserver:self forKeyPath:@"age"]; 
复制代码

2、KVO的实现

KVO 在apple文档的说明

Automatic key-value observing is implemented using a technique called 
isa-swizzling… When an observer is registered for an attribute of an object the 
isa pointer of the observed object is modified, pointing to an intermediate class 
rather than at the true class …
复制代码

利用运行时,生成一个对象的子类,并生成子类对象,并替换原来对象的isa指针,重写了set方法。

让我们看看代码

  • 这是我们创建的Myprofile
@interface MyProfile : NSObject
@property (nonatomic,strong) NSString *avatar;
@property (nonatomic,strong) NSString *age;
@property (nonatomic,strong) NSString *name;
@property (nonatomic,strong) NSMutableArray *dataArr;
@property (nonatomic,strong) MyDetail *myDetail;
复制代码
  • 再看viewcontroller
self.myprofile = [[MyProfile alloc]init];
    self.myprofile.name = @"sallen";
    NSLog(@"before:%s",object_getClassName(self.myprofile));
    [self.myprofile addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
    self.myprofile.name = @"slim";
    NSLog(@"after:%s",object_getClassName(self.myprofile));
复制代码

1.通过打印可以看出class明显发生了变化,监听之后的class替换了原有classisa指针



2.再看看子类

self.myprofile = [[MyProfile alloc]init];
    self.myprofile.name = @"sallen";
    NSLog(@"before:%@",[self findSubClass:[self.myprofile class]]);
    [self.myprofile addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
    self.myprofile.name = @"slim";
    NSLog(@"after:%@",[self findSubClass:[self.myprofile class]]);
复制代码

通过打印可以看出明显多了个子类



3.对于容器的监听

[self.myprofile addObserver:self forKeyPath:@"dataArr" options:NSKeyValueObservingOptionNew context:nil];
 [self.myprofile.dataArr addObject:@"slim"];
复制代码

通过监听数组发现,是没有触发的通知的,因为重写了set方法。
我们可以利用kvc实现对数组的监听

[[self.myprofile mutableArrayValueForKeyPath:@"dataArr"] addObject:@"slim"];
复制代码



4.多级路径属性

Myprofile类里又包含了MyDetail
Mydetail创建了content属性 如果我们需要监听myDetail属性的变化 我们在Myprofile.m通过方法:+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key, 一个Key观察多个属性值的改变。

+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key{
    
    NSSet *keySet = [super keyPathsForValuesAffectingValueForKey:key];
    if ([key isEqualToString:@"myDetail"]) {
        NSSet *set = [NSSet setWithObject:@"_myDetail.content"];
        keySet = [keySet setByAddingObjectsFromSet:set];
    }
    
    return keySet;
}
复制代码

打印结果:



四、KVO的缺陷

KVO很强大,但是也有缺点

1.只能重写 -observeValueForKeyPath:ofObject:change:contex这个方法 来获得通知,不能使用自定义的selector, 想要传一个block更是不可能 ,而且还要处理父类的情况 父类同样观察一个同样的属性的情况 ,但是有时候并不知道父类 是不是对这个消息有兴趣。
2.父类和子类同时存在KVO时,很容易出现对同一个keyPath进行两次removeObserver操作,从而导致程序crash。要避免这个问题,就需要区分出KVOself注册的,还是superClass注册的,我们可以在 -addObserver:forKeyPath:options:context:-removeObserver:forKeyPath:context这两个方法中传入不同的context进行区分。

五、block方式的实现

等待更新,