前言
KVO全称为Key Value Observing,键值监听机制,由NSKeyValueObserving协议提供支持,NSObject类继承了该协议,所以NSObject的子类都可使用该方法。
KVO监听写法
例如在XZPerson类中有这么几个属性
@interface XZPerson : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *nick;
@property (nonatomic, copy) NSString *downloadProgress;
@property (nonatomic, assign) double writtenData;
@property (nonatomic, assign) double totalData;
@property (nonatomic, strong) NSMutableArray *dateArray;
@property (nonatomic, strong) XZStudent *st;
@end
1.我们要监听name属性写法如下
- (void)viewDidLoad {
[super viewDidLoad];
self.person = [XZPerson new];
[self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:NULL];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
NSLog(@"XZController--%@",change);
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
self.person.name = @"alan";
}
在touchBegan方法中变化name值,observeValue中会得到变化;
这里如果我们要监听另一个XZStudent 继承于XZPerson类的并实现单利中也有name属性呢,很多人写法和上面一样,然后通过keyPath 来进行判断处理不同事项,
@interface XZStudent :XZPerson
+ (instancetype)shareInstance;
@end
@implementation XZStudent
static XZStudent* _instance = nil;
+ (instancetype)shareInstance{
static dispatch_once_t onceToken ;
dispatch_once(&onceToken, ^{
_instance = [[super allocWithZone:NULL] init] ;
}) ;
return _instance ;
}
@end
我来介绍另一种使用context 进行标记,官网上介绍,使用context更加安全,方便
翻译:
addObserver:forKeyPath:options:context:message中的上下文指针包含将在相应的更改通知中传递回观察员的任意数据。您可以指定NULL并完全依赖于密钥路径字符串来确定更改通知的来源,但是这种方法可能会导致对象出现问题,该对象的超类由于不同的原因也在观察相同的密钥路径。
一种更安全、更可扩展的方法是使用上下文来确保接收到的通知是发送给观察者的,而不是一个超类。
类中唯一命名的静态变量的地址是一个很好的上下文。在父类或子类中以类似方式选择的上下文不太可能重叠。您可以为整个类选择一个上下文,并依赖通知消息中的密钥路径字符串来确定更改的内容。或者,您可以为每个观察到的密钥路径创建一个不同的上下文,这样可以完全绕过字符串比较的需要,从而提高通知解析的效率。清单1显示了这样选择的balance和interestRate属性的示例上下文。
static void *PersonNameContext = &PersonNameContext;
static void *StudentNameContext = &StudentNameContext;
- (void)viewDidLoad {
[super viewDidLoad];
self.person = [XZPerson new];
self.student = [XZStudent shareInstance];
[self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:PersonNameContext];
// 1. 使用context进行标记,更安全,更加便利
[self.student addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:StudentNameContext];
}
//回调
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
//context
if (context == PersonNameContext) {
NSLog(@"XZController--person -->%@",change);
}else if(context == StudentNameContext)
{
NSLog(@"XZController--person -->%@",change);
}else{
NSLog(@"XZController--%@",change);
}
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
self.person.name = @"alan";
self.student.name = @"alanStudent";
}
如果有XZController中监听了XZStudent的name属性,下级界面XZDetailController中也监听XZStudent的name属性,这个时候,如果在delloc中没有释放的话,返回后XZController中修改name值时就会报野指针,所以切记delloc中一定要进行析构释放
- (void)dealloc
{
[self.person removeObserver:self forKeyPath:@"name"];
[self.student removeObserver:self forKeyPath:@"name"];
}
2.如果需求多变,我们需要频繁更换监听值时
1.可以先把自动监听去掉automaticallyNotifiesObserversForKey 返回NO
2.然后需要监听那个值在值改变前后添加willChangeValueForKey,和didChangeValueForKey方法这个可以写入到set方法中
例如:添加上automaticallyNotifiesObserversForKey(慎用)方法后其他的监听不会自动触发
自动开关,设置为NO后,需要手动触发Willchange和didchange
+ (BOOL) automaticallyNotifiesObserversForKey:(NSString *)key{
return NO;
}
- (void)setNick:(NSString *)nick{
[self willChangeValueForKey:@"nick"];
_nick = nick;
[self didChangeValueForKey:@"nick"];
}
3.监听下载进度时,实际需要在写入进度和总进度这两个进行变化时触发下载进度
首先在XZPerson类中实现复合嵌套keyPathsForValuesAffectingValueForKey方法
// 下载进度 -- writtenData/totalData
+ (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key{
NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];
if ([key isEqualToString:@"downloadProgress"]) {
NSArray *affectingKeys = @[@"totalData", @"writtenData"];
keyPaths = [keyPaths setByAddingObjectsFromArray:affectingKeys];
}
return keyPaths;
}
然后进行监听downloadProgress即可
- (void)dealloc
{
[self.person removeObserver:self forKeyPath:@"downloadProgress"];
}
- (void)viewDidLoad {
[super viewDidLoad];
self.person = [XZPerson new];
self.person.writtenData = 0;
self.person.totalData = 100;
[self.person addObserver:self forKeyPath:@"downloadProgress" options:NSKeyValueObservingOptionNew context:NULL];
}
}
//回调
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
//context
if (context == PersonNameContext) {
NSLog(@"XZController--person -->%@",change);
}else if(context == StudentNameContext)
{
NSLog(@"XZController--person -->%@",change);
}else{
NSLog(@"XZController--%@",change);
}
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
self.person.writtenData += 10;
self.person.totalData +=20;
}
打印结果:可以看出当writtenData变化时有变化,totalData变化时也有变化(这个场景主要用于下载任务,因为下载总任务量也是有可能进行持续添加的)
4.监听可变数组时
这里在变化的数组变化的写法上需要有些区别,而且需要在XZPerson类中添加如下方法:添加插入和删除方法
@implementation XZPerson
-(void)insertObject:(id)object inDateArrayAtIndex:(NSUInteger)index{
[self.dateArray insertObject:object atIndex:index];
}
-(void)removeObjectFromDateArrayAtIndex:(NSUInteger)index{
[self.dateArray removeObjectAtIndex:index];
}
@end
而且在变化值是也需要有所变化
- (void)dealloc
{
[self.person removeObserver:self forKeyPath:@"dateArray"];
}
- (void)viewDidLoad {
[super viewDidLoad];
self.person = [XZPerson new];
self.person.dateArray = [NSMutableArray array];
[self.person addObserver:self forKeyPath:@"dateArray" options:NSKeyValueObservingOptionNew context:NULL];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
//context
if (context == PersonNameContext) {
NSLog(@"XZController--person -->%@",change);
}else if(context == StudentNameContext)
{
NSLog(@"XZController--person -->%@",change);
}else{
NSLog(@"XZController--%@",change);
}
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
//数组变化这么写是不会监听到的
[self.person.dateArray addObject:@"1"];
//KVO 是建立在KVC
[[self.person mutableArrayValueForKey:@"dateArray"] addObject:@"2"];
}
可以看下打印结果:只有一次变化([self.person.dateArray addObject:@"1"];)这次变化没有监听到,注意写法
总结
这篇文章主要描述了一下KVO的简单实用,其中包括监听单个属性,监听多个变化属性,和监听可变数组的一些写法,希望对大家有用,后续文章会详细介绍KVO的底层实现,和自定义KVO的写法;
希望对大家有用处,欢迎大家点赞,关注我的CSDN,我会定期做一些技术分享!未完待续。。。