KVO
-
概述
KVO是key-value-observe的简称,键值观察者,是一种设计模式–观察者模式。也就是当被观察者的状态发生改变时,会通知给观察者,观察者在对应的方法里可以获取相关信息。监听对象属性的变化,对属性才会有作用,一般继承自NSobject的对象都默认支持KVO,KVO监听的是属性的set方法, -
简单使用
- 注册观察者
方法: A addObserver: B forKeyPath: options: context:
参数含义:
1. A 为被观察者;B(observe):观察者,监听属性变化的对象。该对象必须要实现observe ValueForKeyPath:ofObjec:change:context:方法
2. keyPath: 要观察的属性的名称,要和属性声明的名称一致。
3. options:对KVO机制进行配置,修改KVO通知的时机以及通知的内容,决定提供给观察者change字典中的具体信息有哪些
4. context:传入任意类型的对象,在“接收消息回调”的代码中可以接收到这个对象,可以写为nil
options解析
typedef NS_OPTIONS(NSUInteger, NSKeyValueObservingOptions) {
表示监听对象的新值,即变化后的值,change字典中会包含该key的键值对,通过该key即new,就可以取到属性变化后的值
NSKeyValueObservingOptionNew = 0x01,
表示监听对象的新值,即变化前的值,change字典中会包含该key的键值对,通过该key,即old,就可以取到属性变化后的值
NSKeyValueObservingOptionOld = 0x02,
在注册观察者的方法return的时候就会发出一次通知,比如:在viewDidLoad方法运行完,通知就发出去了
NSKeyValueObservingOptionInitial API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0)) = 0x04,
会在值改变前发出一次通知,改变后的通知依旧会发出,也就是每次change都会有两个通知
NSKeyValueObservingOptionPrior API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0)) = 0x08
};
如下例:为a_Student注册一个观察者,观察它的name属性,提供给change变化后的值和变化前的值
[_aStudent addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:nil];
- 实现回调
方法:observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
参数:
keyPath: 所观察对象的属性
object:所观察的对象
change:属性值的变化
change的解析
NSKeyValueChangeKey枚举
FOUNDATION_EXPORT NSKeyValueChangeKey const NSKeyValueChangeKindKey;
FOUNDATION_EXPORT NSKeyValueChangeKey const NSKeyValueChangeNewKey;
FOUNDATION_EXPORT NSKeyValueChangeKey const NSKeyValueChangeOldKey;
FOUNDATION_EXPORT NSKeyValueChangeKey const NSKeyValueChangeIndexesKey;
FOUNDATION_EXPORT NSKeyValueChangeKey const NSKeyValueChangeNotificationIsPriorKey
NSKeyValueChange枚举
typedef NS_ENUM(NSUInteger, NSKeyValueChange) {
NSKeyValueChangeSetting = 1,
NSKeyValueChangeInsertion = 2,
NSKeyValueChangeRemoval = 3,
NSKeyValueChangeReplacement = 4,
};
当change[NSKeyValueChangeKindKey]是NSKeyValueChangeSetting的时候,说明被观察的属性的setter方法被调用了。
inser,remove,replace:被观察者属性是集合类型,且对它进行inser,remove,replace操作的时候会返回这三种Key
如下,当keyPath,即观察者的属性为name时,打印出其变化前后的值
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
if ([keyPath isEqualToString:@"name"]) {
NSLog(@"old name: %@", [change objectForKey:@"old"]);
NSLog(@"new name: %@", [change objectForKey:@"new"]);
}
}
- 移除观察者
- (void)dealloc {
//根据对应的观察者的属性进行移除
[_aStudent removeObserver:self forKeyPath:@"name"];
}
- 小小的注意
//初识的值
_aStudent.name = 12;
//进行改变时,将值继续赋为12
_aStudent.name = 12;
此时仍然会发出通知,因为此时执行了set方法,触发了监听,为了避免这一情况,可以手动设置KVO,上面的是自动KVO,也就是,在类中添加属性时是自动生成对应的getter/setter方法的。
-
具体的例子
Student是我自己创建的一个类
@interface Student : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSString *age;
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.nameTextField = [[UITextField alloc] init];
[self.view addSubview:_nameTextField];
_nameTextField.frame = CGRectMake(100, 100, 200, 40);
_nameTextField.placeholder = @"请输入姓名";
_nameTextField.layer.borderColor = [UIColor blackColor].CGColor;
_nameTextField.layer.borderWidth = 1;
_addButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[self.view addSubview:_addButton];
_addButton.frame = CGRectMake(100, 160, 80, 40);
[_addButton setTitle:@"改变" forState:UIControlStateNormal];
[_addButton addTarget:self action:@selector(clickchange) forControlEvents:UIControlEventTouchUpInside];
_aStudent = [[Student alloc] init];
_aStudent.name = @"123";//先设初值
_aStudent.age = @"12";
[_aStudent addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:nil];
}
-(void)clickchange {
if (_nameTextField.text) {
//触发
_aStudent.name = _nameTextField.text;
}
}
//当kvo事件到来时,会调用这个方法
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
if ([keyPath isEqualToString:@"name"]) {
NSLog(@"old name: %@", [change objectForKey:@"old"]);
NSLog(@"new name: %@", [change objectForKey:@"new"]);
}
}
//移除
- (void)dealloc {
[_aStudent removeObserver:self forKeyPath:@"name"];
}
通知,代理,KVO的区别
- 通知NSNotification。通知中心NSNotificationCenter
使用范围:任何对象之间都可以传递消息,一个对象可以发通知给多个对象,一个对象也可以接收多个对象发出的通知 - KVO
仅仅只能监听对象属性的改变,灵活度不太高 - 代理
一个对象只能设置一个代理,(假设这个对象只有一个代理属性)
一个对象能成为多个对象的代理