OC KVO模式
简介
KVO是Key-Value Observing的简称,也就是键值观察。和广播通知一样,是ios中的一种通知机制,允许对象监听另一个对象特定属性的改变,并在改变时接收到事件。由于KVO的实现机制,所以对属性才会发生作用,一般继承自NSObject的对象都默认支持KVO。
KVO和NSNotificationCenter都是iOS中观察者模式的一种实现。区别在于,相对于被观察者和观察者之间的关系,KVO是一对一的,而不一对多的。KVO对被监听对象无侵入性,不需要修改其内部代码即可实现监听。
这是一张图片乔布斯同学,成绩不好没及格,但是他计算机很牛逼,学校为了防止他入侵系统修改分数,设置了一个报警系统--分数被修改就发出通知。当然实现这个需求的方法很多,这次就用kvo试试。
首先设置一个Model里面放上name和score
#import <Foundation/Foundation.h>
@interface StudentModel : NSObject
@property (nonatomic, copy) NSString *name;
@property float score;
@end
然后就是搞UI和添加观察者
#import "ViewController.h"
#import "StudentModel.h"
//设备的宽高
#define SCREENWIDTH [UIScreen mainScreen].bounds.size.width
#define SCREENHEIGHT [UIScreen mainScreen].bounds.size.height
@interface ViewController ()
@property (nonatomic, strong) StudentModel *studentModel;
@property (nonatomic, strong) UILabel *scoreLabel;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor darkGrayColor];
// 实例化并设置监听
self.studentModel = [[StudentModel alloc] init];
[self.studentModel setValue:@"乔布斯" forKey:@"name"];
[self.studentModel setValue:@"59.0" forKey:@"score"];
//创建观察者
/***
* self.studentModel:被观察的对象
* self:观察者
* score:被观察的键
* options:一个枚举,后面详细介绍
* context:这里可以传值
***/
[self.studentModel addObserver:self forKeyPath:@"score" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:@"分数被改变了"];
// 界面内容
// 名字
UILabel *nameLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 200, SCREENWIDTH, 20)];
nameLabel.text = [NSString stringWithFormat:@"Name:%@", [self.studentModel valueForKey:@"name"]];
nameLabel.textColor = [UIColor whiteColor];
nameLabel.textAlignment = NSTextAlignmentCenter;
[self.view addSubview:nameLabel];
// 分数
self.scoreLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 250, SCREENWIDTH, 20)];
self.scoreLabel.text = [NSString stringWithFormat:@"Score:%@", [self.studentModel valueForKey:@"score"]];
self.scoreLabel.textColor = [UIColor whiteColor];
self.scoreLabel.textAlignment = NSTextAlignmentCenter;
[self.view addSubview:self.scoreLabel];
// 按钮
UIButton *btn = [[UIButton alloc] initWithFrame:CGRectMake((SCREENWIDTH - 100)/2, 300, 100, 20)];
[btn setTitle:@"修改分数" forState:UIControlStateNormal];
[btn addTarget:self action:@selector(changeScore) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:btn];
// [self.studentModel removeObserver:self forKeyPath:@"score"];// 4.移除观察者
}
// 按钮响应
- (void)changeScore {
[self.studentModel setValue:@"99.0" forKey:@"score"];
}
这里我们就需要kvo干事情了
// KVO回调
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
if ([keyPath isEqualToString:@"score"]) {
self.scoreLabel.text = [NSString stringWithFormat:@"Score:%@", [self.studentModel valueForKey:@"score"]];
}
NSLog(@"keyPath:%@",keyPath);//被观察的键
NSLog(@"object:%@",object);//被观察者
NSLog(@"context:%@", context);// 通过context获取被观察者传递的内容
NSLog(@"change:%@", change);// 根据change的设置获取新值、旧值等
NSLog(@"新值:%@",change[@"new"]);
NSLog(@"旧值:%@",change[@"old"]);
}
点击按钮后的输出结果为:
2019-08-19 15:31:02.868719+0800 KVODemo[2660:437306] keyPath:score
2019-08-19 15:31:02.868838+0800 KVODemo[2660:437306] object:<StudentModel: 0x600001bc3340>
2019-08-19 15:31:02.868918+0800 KVODemo[2660:437306] context:分数被改变了
2019-08-19 15:31:02.869062+0800 KVODemo[2660:437306] change:{
kind = 1;
new = 99;
old = 59;
}
2019-08-19 15:31:02.869130+0800 KVODemo[2660:437306] 新值:99
2019-08-19 15:31:02.869217+0800 KVODemo[2660:437306] 旧值:59
然后在适当的时候移除观察者
[self.studentModel removeObserver:self forKeyPath:@"score"]
options参数
在添加观察者时有一个options参数,在回调获取变化时有一个change参数,这两个参数其实是对应的,都是用来增加传递变化的丰富度。
options参数可以设为:
-
NSKeyValueObservingOptionOld:这表示在回调获取变化时可以通过change参数获取变化之前的值;
-
NSKeyValueObservingOptionNew:这表示在回调获取变化时可以通过change参数获取变化后的值;
-
NSKeyValueObservingOptionInitial:在添加观察者方法return的时候就发出一次通知;
-
NSKeyValueObservingOptionPrior:会在观察的值发生变化前发出一次通知,变化后还是会发出一次通知,所以变化一次一共会得到两次通知。
change参数
在使用change的时候可以通过下面的key来操作:
-
NSKeyValueChangeKindKey:对应NSKeyValueChange的枚举值
1: NSKeyValueChangeSetting = 1:说明被观察的数据的setter方法被调用了;2:NSKeyValueChangeInsertion = 2:当观察的数据是集合时,且对它进行insert操作时会返回该值;
3:NSKeyValueChangeRemoval = 3:当观察的数据是集合时,且对它进行remove操作时会返回该值;
4:NSKeyValueChangeReplacement = 4:当观察的数据是集合时,且对它进行replace操作时会返回该值。
-
NSKeyValueChangeNewKey:对应options参数中的NSKeyValueObservingOptionNew,会在其中包含观察的数据变化后的新值
-
NSKeyValueChangeOldKey:对应options参数中的NSKeyValueObservingOptionOld,会在其中包含观察的数据变化之前得旧值
-
NSKeyValueChangeIndexesKey:当NSKeyValueChangeKindKey是2、3、4的时候,也就是说是观察集合数据时,这个key的值是一个NSIndexSet,包含操作对象的索引集合
-
NSKeyValueChangeNotificationIsPriorKey:包含一个布尔值,如果options的参数是NSKeyValueObservingOptionPrior,也就是会通知两次,在第一次通知,也就是改变前的通知时,会包含这个key