簡介
KVO是Key-Value Observing的簡稱册养,也就是鍵值觀察。和廣播通知一樣压固,是ios中的一種通知機(jī)制捕儒,允許對(duì)象監(jiān)聽另一個(gè)對(duì)象特定屬性的改變,并在改變時(shí)接收到事件。由于KVO的實(shí)現(xiàn)機(jī)制刘莹,所以對(duì)屬性才會(huì)發(fā)生作用,一般繼承自NSObject的對(duì)象都默認(rèn)支持KVO焚刚。
KVO和NSNotificationCenter都是iOS中觀察者模式的一種實(shí)現(xiàn)点弯。區(qū)別在于,相對(duì)于被觀察者和觀察者之間的關(guān)系矿咕,KVO是一對(duì)一的抢肛,而不一對(duì)多的。KVO對(duì)被監(jiān)聽對(duì)象無侵入性碳柱,不需要修改其內(nèi)部代碼即可實(shí)現(xiàn)監(jiān)聽捡絮。
喬布斯同學(xué),成績不好沒及格莲镣,但是他計(jì)算機(jī)很牛逼福稳,學(xué)校為了防止他入侵系統(tǒng)修改分?jǐn)?shù),設(shè)置了一個(gè)報(bào)警系統(tǒng)--分?jǐn)?shù)被修改就發(fā)出通知瑞侮。當(dāng)然實(shí)現(xiàn)這個(gè)需求的方法很多的圆,這次就用kvo試試。
首先設(shè)置一個(gè)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"
//設(shè)備的寬高
#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];
// 實(shí)例化并設(shè)置監(jiān)聽
self.studentModel = [[StudentModel alloc] init];
[self.studentModel setValue:@"喬布斯" forKey:@"name"];
[self.studentModel setValue:@"59.0" forKey:@"score"];
//創(chuàng)建觀察者
/***
* self.studentModel:被觀察的對(duì)象
* self:觀察者
* score:被觀察的鍵
* options:一個(gè)枚舉半火,后面詳細(xì)介紹
* context:這里可以傳值
***/
[self.studentModel addObserver:self forKeyPath:@"score" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:@"分?jǐn)?shù)被改變了"];
// 界面內(nèi)容
// 名字
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];
// 分?jǐn)?shù)
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:@"修改分?jǐn)?shù)" forState:UIControlStateNormal];
[btn addTarget:self action:@selector(changeScore) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:btn];
// [self.studentModel removeObserver:self forKeyPath:@"score"];// 4.移除觀察者
}
// 按鈕響應(yīng)
- (void)changeScore {
[self.studentModel setValue:@"99.0" forKey:@"score"];
}
這里我們就需要kvo干事情了
// KVO回調(diào)
- (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獲取被觀察者傳遞的內(nèi)容
NSLog(@"change:%@", change);// 根據(jù)change的設(shè)置獲取新值越妈、舊值等
NSLog(@"新值:%@",change[@"new"]);
NSLog(@"舊值:%@",change[@"old"]);
}
點(diǎn)擊按鈕后的輸出結(jié)果為:
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:分?jǐn)?shù)被改變了
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
然后在適當(dāng)?shù)臅r(shí)候移除觀察者
[self.studentModel removeObserver:self forKeyPath:@"score"]
options參數(shù)
在添加觀察者時(shí)有一個(gè)options參數(shù),在回調(diào)獲取變化時(shí)有一個(gè)change參數(shù)钮糖,這兩個(gè)參數(shù)其實(shí)是對(duì)應(yīng)的梅掠,都是用來增加傳遞變化的豐富度。
options參數(shù)可以設(shè)為:
NSKeyValueObservingOptionOld:這表示在回調(diào)獲取變化時(shí)可以通過change參數(shù)獲取變化之前的值店归;
NSKeyValueObservingOptionNew:這表示在回調(diào)獲取變化時(shí)可以通過change參數(shù)獲取變化后的值阎抒;
NSKeyValueObservingOptionInitial:在添加觀察者方法return的時(shí)候就發(fā)出一次通知;
NSKeyValueObservingOptionPrior:會(huì)在觀察的值發(fā)生變化前發(fā)出一次通知娱节,變化后還是會(huì)發(fā)出一次通知挠蛉,所以變化一次一共會(huì)得到兩次通知。
change參數(shù)
在使用change的時(shí)候可以通過下面的key來操作:
-
NSKeyValueChangeKindKey:對(duì)應(yīng)NSKeyValueChange的枚舉值
1: NSKeyValueChangeSetting = 1:說明被觀察的數(shù)據(jù)的setter方法被調(diào)用了肄满;2:NSKeyValueChangeInsertion = 2:當(dāng)觀察的數(shù)據(jù)是集合時(shí)谴古,且對(duì)它進(jìn)行insert操作時(shí)會(huì)返回該值;
3:NSKeyValueChangeRemoval = 3:當(dāng)觀察的數(shù)據(jù)是集合時(shí)稠歉,且對(duì)它進(jìn)行remove操作時(shí)會(huì)返回該值掰担;
4:NSKeyValueChangeReplacement = 4:當(dāng)觀察的數(shù)據(jù)是集合時(shí),且對(duì)它進(jìn)行replace操作時(shí)會(huì)返回該值怒炸。
NSKeyValueChangeNewKey:對(duì)應(yīng)options參數(shù)中的NSKeyValueObservingOptionNew带饱,會(huì)在其中包含觀察的數(shù)據(jù)變化后的新值
NSKeyValueChangeOldKey:對(duì)應(yīng)options參數(shù)中的NSKeyValueObservingOptionOld,會(huì)在其中包含觀察的數(shù)據(jù)變化之前得舊值
NSKeyValueChangeIndexesKey:當(dāng)NSKeyValueChangeKindKey是2、3勺疼、4的時(shí)候教寂,也就是說是觀察集合數(shù)據(jù)時(shí),這個(gè)key的值是一個(gè)NSIndexSet执庐,包含操作對(duì)象的索引集合
NSKeyValueChangeNotificationIsPriorKey:包含一個(gè)布爾值酪耕,如果options的參數(shù)是NSKeyValueObservingOptionPrior,也就是會(huì)通知兩次轨淌,在第一次通知迂烁,也就是改變前的通知時(shí),會(huì)包含這個(gè)key