KVO 简述
KVO<NSKeyValueObserving>,是一个非正式协议,它定义了对象之间观察和通知状态改变的通用机制。
我们可以监听一个对象的属性,包括简单属性,一对一的关系,和一对多的关系。一对多关系的监听者会被告知集合变更的类型,以及哪些对象参与了变化。
NSObject提供了一个NSKeyValueObserving协议的默认实现,它为所有对象提供了一种自动发送修改通知的能力。我们可以通过禁用自动发送通知并使用这个协议提供的方法来手动实现通知的发送,以便更精确地去处理通知。
基础方法的讲解
使用KVO必须要满足的条件和一般使用步骤
1.该对象必须支持KVC(凡是继承自NSObject的类都支持KVC)
2.作为观察者的对象必须实现 -(void)observeValueForKeyPath:ofObject:change:context: 方法
3.被观察的对象要用- (void)addObserver:forKeyPath:options:context:方法注册观察者
4.用完要移除。附上方法- (void)removeObserver:forKeyPath: 或者- (void)removeObserver:forKeyPath:context:
然后我们对KVO方法进行详细的讲解。
1.- (void)addObserver:forKeyPath:options:context:观察对象的添加 以及被观察者的确定
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
解析:
1.此方法为减号方法 调用者即为被观察者。
-
observer 此参数为观察者 也就是监控者 它可以通过实现
-(void)observeValueForKeyPath:ofObject:change:context:对被观察者进行观察。 -
forKeyPath 所观察属性的名字(宏观看法,后续会详解)
4.options 这个参数可以理解为所需观察的设置选项
可以很清楚的看到,其中包含了四种值,分别为:
NSKeyValueObservingOptionNew:提供更改前的值
NSKeyValueObservingOptionOld:提供更改后的值
NSKeyValueObservingOptionInitial:观察最初的值(在注册观察服务时会调用一次触发方法)
NSKeyValueObservingOptionPrior:分别在值修改前后触发方法(即一次修改有两次触发)
稍后会对此进行案例的分析。
2.-(void)observeValueForKeyPath:ofObject:change:context: 观察者观察方法的实现
- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context;
-
keyPath 被观察的属性 上个方法中已经讲解 属于同一个参数
-
object被观察者所属的对象 也就是本官者本身
-
change 这是一个字典,它包含了属性被修改的一些信息。
这个字典中包含的值会根据我们在添加观察者时(addObserver方法)设置的options参数有所变化。 -
context 添加观察者时的上下文信息,它可以被用作区分那些绑定同一个keypath的不同对象的观察者。
比如说观察一些继承自同一个父类的子类,而这些子类都有一个相同的keyPath。
context:(nullable void *)context;
关于方法讲解 我们到此为止。我们接下来进行案例分析
3- (void)removeObserver:forKeyPath: 或者- (void)removeObserver:forKeyPath:context:
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context API_AVAILABLE(macos(10.7), ios(5.0), watchos(2.0), tvos(9.0));
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
注意:
可能导致崩溃或内容出错的情况
1.被观察的对象销毁掉了(被观察的对象是一个局部变量)
2.观察者被释放掉了,但是没有移除监听(如模态推出,push,pop等)
2.注册的监听没有移除掉,又重新注册了一遍监听
4 案例分析
1. Person.h
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property (nonatomic,copy)NSString *age;
@end
2. Person.m
#import "Person.h"
@implementation Person
@end
3.调用 options
3.1 options ---> NSKeyValueObservingOptionNew
#import "ViewController.h"
#import "Person.h"
@interface ViewController ()
@property (nonatomic,strong)Person *person;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
//1.注册观察者,实施监听
self.person = [[Person alloc]init];
[self.person addObserver:self
forKeyPath:@"age"
options:NSKeyValueObservingOptionNew
context:nil];
self.person.age = @"18";
}
- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context{
NSLog(@"keyPath:%@",keyPath);
NSLog(@"object:%@",object);
NSLog(@"change:%@",change);
}
打印数据:
2018-05-21 15:32:11.792849+0700 KVODemo[51410:1650569] keyPath:age
2018-05-21 15:32:11.793007+0700 KVODemo[51410:1650569] object:<Person: 0x600000011660>
2018-05-21 15:32:11.793198+0700 KVODemo[51410:1650569] change:{
kind = 1;
new = 18;
}
3.2 options ---> NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld
打印数据:
2018-05-21 15:35:28.030530+0700 KVODemo[51467:1652953] keyPath:age
2018-05-21 15:35:28.030690+0700 KVODemo[51467:1652953] object:<Person: 0x604000017fd0>
2018-05-21 15:35:28.030885+0700 KVODemo[51467:1652953] change:{
kind = 1;
new = 18;
old = "<null>";
}
3.3 options ---> NSKeyValueObservingOptionInitial
2018-05-21 15:42:56.834653+0700 KVODemo[51666:1660465] keyPath:age
2018-05-21 15:42:56.834798+0700 KVODemo[51666:1660465] object:<Person: 0x604000205260>
2018-05-21 15:42:56.834965+0700 KVODemo[51666:1660465] change:{
kind = 1;
}
2018-05-21 15:42:56.835267+0700 KVODemo[51666:1660465] keyPath:age
2018-05-21 15:42:56.835364+0700 KVODemo[51666:1660465] object:<Person: 0x604000205260>
2018-05-21 15:42:56.835492+0700 KVODemo[51666:1660465] change:{
kind = 1;
}
总结:我们发现当options 是 NSKeyValueObservingOptionInitial监听方法只在注册观察者者的时候 调用了一次 所以此方法一般用的不多,可以用作初始值的获取。
3.4 options ---> NSKeyValueObservingOptionPrior
#import "ViewController.h"
#import "Person.h"
@interface ViewController ()
@property (nonatomic,strong)Person *person;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
//1.注册观察者,实施监听
self.person = [[Person alloc]init];
[self.person addObserver:self
forKeyPath:@"age"
options:NSKeyValueObservingOptionPrior
context:nil];
self.person.age = @"18";
self.person.age = @"19";
}
- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context{
NSLog(@"keyPath:%@",keyPath);
NSLog(@"object:%@",object);
NSLog(@"change:%@",change);
}
打印数据:
2018-05-21 15:48:51.106960+0700 KVODemo[51814:1666067] keyPath:age
2018-05-21 15:48:51.107172+0700 KVODemo[51814:1666067] object:<Person: 0x60000000ba00>
2018-05-21 15:48:51.107394+0700 KVODemo[51814:1666067] change:{
kind = 1;
notificationIsPrior = 1;
}
2018-05-21 15:48:51.107520+0700 KVODemo[51814:1666067] keyPath:age
2018-05-21 15:48:51.107615+0700 KVODemo[51814:1666067] object:<Person: 0x60000000ba00>
2018-05-21 15:48:51.107734+0700 KVODemo[51814:1666067] change:{
kind = 1;
}
2018-05-21 15:48:51.107831+0700 KVODemo[51814:1666067] keyPath:age
2018-05-21 15:48:51.107922+0700 KVODemo[51814:1666067] object:<Person: 0x60000000ba00>
2018-05-21 15:48:51.108045+0700 KVODemo[51814:1666067] change:{
kind = 1;
notificationIsPrior = 1;
}
2018-05-21 15:48:51.108123+0700 KVODemo[51814:1666067] keyPath:age
2018-05-21 15:48:51.108284+0700 KVODemo[51814:1666067] object:<Person: 0x60000000ba00>
2018-05-21 15:48:51.108414+0700 KVODemo[51814:1666067] change:{
kind = 1;
}
总结:
我们发现我们进行两次赋值,但是却调用了四次,正如官方文档解释一般 它会在 调用之前调用之后 分别调用一次。
未完,待续
网友评论