上一篇我们看了KVO的本质,接下来我们来试试使用KVO吧
准备工作:
Person_.h
#import <Foundation/Foundation.h>
@interface Person_ : NSObject
@property (nonatomic,assign)int age;
@end
Person_.m
#import "Person_.h"
@implementation Person_
@end
Student.h
#import <Foundation/Foundation.h>
@interface Student : NSObject
@property (nonatomic,strong)NSString *name;
@end
Student.m
#import "Student.h"
@implementation Student
-(void)dealloc{
NSLog(@"%s",__FUNCTION__);
}
@end
School.h
#import <Foundation/Foundation.h>
#import "Person_.h"
#import "Student.h"
@interface School : NSObject
@property (nonatomic,strong) Person_ *leader;
@property (nonatomic,strong)NSMutableArray<Student*> *stuArr;
@property (nonatomic,assign)int lastTime;
@end
School.m
#import "School.h"
@implementation School
@end
1.观察者的添加和移除观察者
KVOBaseUsesViewController_1.m
@interface KVOBaseUsesViewController_1 ()
@property (nonatomic,strong)School *myhool;
@end
- (void)viewDidLoad {
[super viewDidLoad];
//初始化对象
self.myhool = [[School alloc]init];
Person_ *leader = [[Person_ alloc]init];
leader.age = 60;
self.myhool.leader = leader;
self.myhool.lastTime = 100;
[self addObserver_1];
[self addObserver_2];
[self addObserver_3];
}
----------------------------------------------------------------
//observer观察者对象,值变化时通知的对象
//keyPath监听的属性:是字符串,也可以使用对象属性的属性的字符串
/*
context通知的上下文:
1.当同一个观察者对同一类的不同对象同一属性
2.OR不同类的对象的相同属性进行监听时,可以使用改参数进行区分
*/
/*
NSKeyValueObservingOptions(可以写成“|”的形式哟,来获取前后的变值)
1.NSKeyValueObservingOptionNew:属性更改的新值
2.NSKeyValueObservingOptionOld:属性更改前的旧值
3.NSKeyValueObservingOptionInitial:如果设置了这个值,将会立刻向观察者对象发送一次通知
4.NSKeyValueObservingOptionPrior:设置了该值后会在属性发生改变前和改变后都通知一次
*/
-(void)addObserver_1{
[self.myhool addObserver:self forKeyPath:@"lastTime" options:NSKeyValueObservingOptionNew context:@"lastTime"];
[self.myhool addObserver:self forKeyPath:@"leader.age" options:NSKeyValueObservingOptionOld context:@"leader.age"];
}
-(void)addObserver_2{
//只监听最基本的属性值的改变
[self.myhool addObserver:self forKeyPath:@"lastTime" options:NSKeyValueObservingOptionInitial context:@"lastTime"];
//监听了属性对象里面的属性值的改变
[self.myhool addObserver:self forKeyPath:@"leader.age" options:NSKeyValueObservingOptionPrior context:@"leader.age"];
}
-(void)addObserver_3{
//写成“|”的形式哟
[self.myhool addObserver:self forKeyPath:@"lastTime" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:@"lastTime"];
}
//实现监听的方法
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
NSLog(@"监听到%@的%@属性值改变了 - %@ - %@", object, keyPath, change, context);
}
//点击屏幕改变属性
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
self.myhool.lastTime = 120;
self.myhool.leader.age = 180;
}
//移除观察者
-(void)dealloc{
//全部移除:lastTime的监听
[self.myhool removeObserver:self forKeyPath:@"lastTime"];
//只移除:上下文为->leader.age的监听
[self.myhool removeObserver:self forKeyPath:@"leader.age" context:@"leader.age"];
NSLog(@"%s",__FUNCTION__);
}
注意点:
移除观察者的时候:
添加1次
[self.myhool addObserver:self forKeyPath:@"leader.age" options:NSKeyValueObservingOptionOld context:@"leader.age"];
移除2次:
[self.myhool removeObserver:self forKeyPath:@"leader.age" context:@"leader.age"];
[self.myhool removeObserver:self forKeyPath:@"leader.age" context:@"leader.age"];
此时:会崩溃的哟,弹出exception
-----------------------------------------------
- (void)viewDidLoad {
[super viewDidLoad];
//初始化对象
self.myhool = [[School alloc]init];
Student *stu = [[Student alloc]init];
[self.myhool addObserver:stu forKeyPath:@"lastTime" options:NSKeyValueObservingOptionNew context:nil];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self.myhool removeObserver:stu forKeyPath:@"lastTime"];
});
}
stu测试观察者对象被释放了,再次发送消息,则会崩溃(所以要去移除)(即:add和remove需要成对出现)
2.观察者监听的方法
对于NSKeyValueChangeKey里面的详细说明,后面会详细说明
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
NSLog(@"监听到%@的%@属性值改变了 - %@ - %@", object, keyPath, change, context);
}
3.手动发送通知
#import "KVOBaseUsesViewController_3.h"
@interface Boy : NSObject
@property (nonatomic,strong)NSString *name;
@end
@implementation Boy
/*关闭自动通知 */
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
return ![key isEqualToString:@"name"];
}
/* 手动发送通知 */
- (void)setName:(NSString *)name{
[self willChangeValueForKey:@"name"];
_name = name;
NSLog(@"在改变呢");
[self didChangeValueForKey:@"name"];
NSLog(@"已经改变了值");
}
@end
@interface KVOBaseUsesViewController_3 ()
@property (nonatomic,strong)Boy *myBoy;
@end
@implementation KVOBaseUsesViewController_3
- (void)viewDidLoad {
[super viewDidLoad];
self.myBoy = [[Boy alloc]init];
[self.myBoy addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:nil];
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
NSLog(@"监听到%@的%@属性值改变了 - %@ - %@", object, keyPath, change, context);
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
self.myBoy.name = @"Lucky";
}
-(void)dealloc{
[self.myBoy removeObserver:self forKeyPath:@"name" ];
}
@end
4.监听集合属性(NSMutableArray、NSMutableSet和NSMutableOrderedSet)
准备:
@interface Girl : NSObject
@property (nonatomic,strong)NSMutableArray *clothes;
@end
@implementation Girl
@end
我们先来写一个按照常规的去监听集合属性
@interface KVOBaseUsesViewController_4 ()
@property (nonatomic,strong)Girl *girl;
@end
@implementation KVOBaseUsesViewController_4
- (void)viewDidLoad {
[super viewDidLoad];
self.girl = [[Girl alloc]init];
self.girl.clothes = @[@"010101",@"000",@"111"].mutableCopy;
[self.girl addObserver:self forKeyPath:@"clothes" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
}
点击改变属性:
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[self.girl.clothes addObject:@"222"];
}
//发现:根本不会触发内部发生的变化监听(属性内部的集合里面的值不会监听到)
那怎样实现监听呢?(我们从苹果api说明参考看到如下实现)
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
NSMutableArray *arrM = [self.girl mutableArrayValueForKey:@"clothes"];
NSLog(@"原始arrM = %@",arrM);
[arrM addObject:@"222"];
NSLog(@"添加之后arrM = %@",arrM);
[arrM insertObject:@"333" atIndex:0];
NSLog(@"插入之后arrM = %@",arrM);
[arrM removeObject:@"111"];
NSLog(@"移除之后arrM = %@",arrM);
[arrM replaceObjectAtIndex:0 withObject:@"444"];
NSLog(@"替换之后arrM = %@",arrM);
//数组的其他方法...
}
实现监听的方法:
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
NSLog(@"监听到%@的%@属性值改变了 - %@ - %@", object, keyPath, change, context);
if([keyPath isEqualToString:@"clothes"]) {
/*NSKeyValueChangeKindKey:
*NSKeyValueChange枚举相关的值:如下
enum {
NSKeyValueChangeSetting = 1, //设置一个新值。被监听的属性可以是一个对象,也可以是一对一关系的属性或一对多关系的属性。
NSKeyValueChangeInsertion = 2,// 表示一个对象被插入到一对多关系的属性。
NSKeyValueChangeRemoval = 3,// 表示一个对象被从一对多关系的属性中移除。
NSKeyValueChangeReplacement = 4 // 表示一个对象在一对多的关系的属性中被替换
};typedef NSUInteger NSKeyValueChange;
*/
NSNumber *kindStr = change[NSKeyValueChangeKindKey];
NSLog(@"属性变化的类型:%@",kindStr);
/*NSKeyValueChangeOldKey:
*属性的旧值。当NSKeyValueChangeKindKey是 NSKeyValueChangeSetting,
*且添加观察的方法设置了NSKeyValueObservingOptionOld时,我们能获取到属性的旧值。
*如果NSKeyValueChangeKindKey是NSKeyValueChangeRemoval或者NSKeyValueChangeReplacement,
*且指定了NSKeyValueObservingOptionOld时,则我们能获取到一个NSArray对象,包含被移除的对象或
*被替换的对象
*/
NSArray *oldArray = change[NSKeyValueChangeOldKey];
NSLog(@"旧值:%@",oldArray);
/*NSKeyValueChangeNewKey:
*属性的新值。当NSKeyValueChangeKindKey是 NSKeyValueChangeSetting,
*且添加观察的方法设置了NSKeyValueObservingOptionNew时,我们能获取到属性的新值。
*如果NSKeyValueChangeKindKey是NSKeyValueChangeInsertion或者NSKeyValueChangeReplacement,
*且指定了NSKeyValueObservingOptionNew时,则我们能获取到一个NSArray对象,包含被插入的对象或
*用于替换其它对象的对象。
*/
NSArray *newArray = change[NSKeyValueChangeNewKey];
/*NSKeyValueChangeIndexesKey:
*如果NSKeyValueChangeKindKey的值是NSKeyValueChangeInsertion、NSKeyValueChangeRemoval
*或者NSKeyValueChangeReplacement,则这个key对应的值是一个NSIndexSet对象,
*包含了被插入、移除或替换的对象的索引
*/
NSIndexSet *indexes = change[NSKeyValueChangeIndexesKey];
__block NSInteger i = 0;
[indexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) {
NSLog(@"下标 : %ld, 新值 : %@", idx, newArray[i]);
i++;
}];
}
}
这里说明都有:具体的可参考github上Demo实现
最后我们移除观察者对象
-(void)dealloc{
[self.girl removeObserver:self forKeyPath:@"clothes"];
}
5.不重复发送通知(当属性数据还是跟上一次的一样就不发送通知)
#import "KVOBaseUsesViewController_5.h"
@interface Test : NSObject
@property (nonatomic,strong)NSString *name;
@end
@implementation Test
/*关闭自动通知 */
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
return ![key isEqualToString:@"name"];
}
/* 过滤 */
- (void)setName:(NSString *)name{
if (![name isEqualToString:_name]){ //相同的话就不发送
[self willChangeValueForKey:@"name"];
_name = name;
[self didChangeValueForKey:@"name"];
}
}
@end
@interface KVOBaseUsesViewController_5 ()
@property (nonatomic,strong)Test *test;
@end
@implementation KVOBaseUsesViewController_5
- (void)viewDidLoad {
[super viewDidLoad];
self.test = [[Test alloc]init];
[self.test addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:nil];
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
NSLog(@"监听到%@的%@属性值改变了 - %@ - %@", object, keyPath, change, context);
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
self.test.name = @"Lucky";
}
-(void)dealloc{
[self.test removeObserver:self forKeyPath:@"name"];
}
@end
友情链接:
网友评论