1、KVO的简介

     KVO 全称 Key-Value Observing。中文叫键值观察。KVO其实是一种观察者模式,观察者在键值改变时会得到通知,利用它可以很容易实现视图组件和数据模型的分离,当数据模型的属性值改变之后作为监听器的视图组件就会被激发,激发时就会回调监听器自身。相比Notification,KVO更加的简单直接。

     KVO的操作方法由NSKeyValueCoding提供,而他是NSObject的类别,也就是说ObjC中几乎所有的对象都支持KVO操作。

 

   KVO 需要实现实例变量的 setter/getter方法

2、KVO的实现

 

    0) KVO的使用也很简单,就是简单的3步。

 

  (1)注册需要观察的对象的属性addObserver:forKeyPath:options:context:

 

  (2)实现observeValueForKeyPath:ofObject:change:context:方法,这个方法当观察的属性变化时会自动调用.在这个方法中还通过                 NSKeyValueObservingOptionNew这个参数要求把新值在dictionary中传递过来。

 

  (3)取消注册观察removeObserver:forKeyPath:context:

 

     1)注册

     实现方法:

    - (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable     void *)context;

     方法参数:

     object : 被观察对象

       observer: 观察对象

       forKeyPath里面带上property的name,如UIView的frame、center等等

       options: 有4个值,分别是:

       NSKeyValueObservingOptionNew 把更改之前的值提供给处理方法

       NSKeyValueObservingOptionOld 把更改之后的值提供给处理方法

       NSKeyValueObservingOptionInitial 把初始化的值提供给处理方法,一旦注册,立马就会调用一次。通常它会带有新值,而不会带有旧值。

       NSKeyValueObservingOptionPrior 分2次调用。在值改变之前和值改变之后。

       注:例子里的0就代表不带任何参数进去

       context: 可以带入一些参数,其实这个挺好用的,任何类型都可以,自己强转就好了。

  2)监测 

   实现方法:

     - (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSString*, id>          *)change context:(nullable void *)context;

     这个方法当观察的属性变化时会自动调用.在这个方法中还通过NSKeyValueObservingOptionNew这个参数要求把新值在dictionary中传递过来。

  方法参数:

       keyPath: 对应forKeyPath

       object:  被观察的对象

       change:  对应options里的NSKeyValueObservingOptionNew、NSKeyValueObservingOptionOld等

       context: 对应context

  3)取消注册观察

     实现方法:

  - (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context NS_AVAILABLE(10_7, 5_0);

   方法参数:

  同上

3、具体实现代码

  main.m

 1 //
 2 //  main.m
 3 //  KVO具体实现
 4 //
 5 //  Created by ma c on 16/5/18.
 6 //  Copyright © 2016年 彭盛凇. All rights reserved.
 7 //
 8 
 9 #import <Foundation/Foundation.h>
10 #import "Person.h"
11 #import "Watch.h"
12 
13 int main(int argc, const char * argv[]) {
14     @autoreleasepool {
15         
16         Person *person = [[Person alloc] init];
17         
18         Watch *watch = [[Watch alloc] init];
19         
20         person.watch = watch;
21         
22         [person registerObserver];
23         
24         person.name = @"pss";
25         
26         person.name = @"pbb";
27         
28     }
29     return 0;
30 }
main.m

  person.h

 1 //
 2 //  Person.h
 3 //  KVO具体实现
 4 //
 5 //  Created by ma c on 16/5/18.
 6 //  Copyright © 2016年 彭盛凇. All rights reserved.
 7 //
 8 
 9 #import <Foundation/Foundation.h>
10 #import "Watch.h"
11 
12 @interface Person : NSObject
13 
14 @property (nonatomic, copy) NSString *name;
15 
16 @property (nonatomic, strong) Watch *watch;
17 
18 - (void)registerObserver;
19 
20 @end
Person.h

  Person.m

 1 //
 2 //  Person.m
 3 //  KVO具体实现
 4 //
 5 //  Created by ma c on 16/5/18.
 6 //  Copyright © 2016年 彭盛凇. All rights reserved.
 7 //
 8 
 9 #import "Person.h"
10 
11 @implementation Person
12 
13 - (void)dealloc {
14     
15     [self removeObserver:self.watch forKeyPath:@"name"];
16 }
17 
18 - (void)registerObserver {
19     
20     [self addObserver:self.watch forKeyPath:@"name" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
21 }
22 @end
Person.m

  Watch.m

 1 //
 2 //  Watch.m
 3 //  KVO具体实现
 4 //
 5 //  Created by ma c on 16/5/18.
 6 //  Copyright © 2016年 彭盛凇. All rights reserved.
 7 //
 8 
 9 #import "Watch.h"
10 
11 @implementation Watch
12 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
13     
14     NSLog(@"new = %@",[change valueForKey:@"new"]);
15     
16     NSLog(@"old = %@",[change valueForKey:@"old"]);
17     
18     NSLog(@"---------------------------");
19 }
20 @end
Watch.h

  Log打印日志

 

4、KVO常见崩溃错误与解决方式

  1)没有在准确位置实现dealloc , 注册,删除,监听 操作方法

  2)没有实现dealloc中的remove操作

 

 5、手动实现KVO

 1 + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
 2     
 3     BOOL automatic = YES;
 4     if ([key isEqualToString:@"age"]) {
 5         automatic = NO;
 6     } else {
 7         automatic = [super automaticallyNotifiesObserversForKey:key];
 8     }
 9     
10     return automatic;
11 }
12 
13 - (void)setAge:(int)age {
14     
15     //手动设置KVO
16     
17     if (_age != age) {
18         
19         [self willChangeValueForKey:@"age"];
20 
21         _age = age;
22         
23         [self didChangeValueForKey:@"age"];
24         
25     }
26 }

6、KVO的实现原理

当某一个类的实例第一次使用KVO的时候,系统就会在运行期间动态的创建该类的一个派生类,该类的命名规则一般是以NSKVONotifying为前缀,以原本的类名为后缀。并且将原型的对象的isa指针指向该派生类。同时在派生类中重载了使用KVO的属性的setter方法,在重载的setter方法中实现真正的通知机制,正如前面我们手动实现KVO一样。这么做是基于设置属性会调用 setter 方法,而通过重写就获得了 KVO 需要的通知机制。当然前提是要通过遵循 KVO 的属性设置方式来变更属性值,如果仅是直接修改属性对应的成员变量,是无法实现 KVO 的。