(之前写了文章,因为格式问题,删掉重新发布了)
1.常用的方法就两个,一个是添加,一个是返回!
添加观察
- (void)addObserver:(NSObject*)observer forKeyPath:(NSString*)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void*)context;
代理返回
- (void)observeValueForKeyPath:(nullable NSString*)keyPath ofObject:(nullable id)object change:(nullable NSDictionary *)change context:(nullable void*)context;
具体实现如下图
- (void)viewDidLoad {
[super viewDidLoad];
Person *p = [[Person alloc]init];
p.name = @"阿牛";
[p addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:NULL];
_p = p;
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
NSLog(@"%@",change);
}
肯定有朋友好奇,为什么最后一个参数是NULL;
苹果官方文档-KVO
Context
The context pointer in the addObserver:forKeyPath:options:context: message contains arbitrary data that will be passed back to the observer in the corresponding change notifications. You may specify NULL and rely entirely on the key path string to determine the origin of a change notification, but this approach may cause problems for an object whose superclass is also observing the same key path for different reasons.>
官方文档白纸黑字,NULL!
2.options有四个枚举,分别对应什么意思呢?
NSKeyValueObservingOptionNew --返回新的值
NSKeyValueObservingOptionOld --返回旧的值
NSKeyValueObservingOptionInitial --注册的时候会返回一次,改变的后又会返回一次
NSKeyValueObservingOptionPrior --改变之前返回一次,改变之后返回一次;
3.记住,默认是自动发送通知,可以修改为手动发送通知。
挺简单的,就一个类方法,
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString*)key;
怎么修改为手动模式呢?
我们以具体代码来实现,
把Person设置为手动,在.m文件添加方法
#import "Person.h"
@implementation Person
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key{
return NO;
}
@end
设置为手动以后,又该如何发送通知呢?
- (void)willChangeValueForKey:(NSString *)key;
- (void)didChangeValueForKey:(NSString *)key;
具体实现如下:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
[_p willChangeValueForKey:@"name"];
_p.name = @"阿猫";
NSLog(@"%@",change);
[_p didChangeValueForKey:@"name"];
}
4.如何监听嵌套型呢(属性里面的属性)?
我有一个Person类,现在Person类里面,又有一个Cat类,Cat类里面,有一个name属性;
我们可以用“.”语法来监听,贴上代码;
我们将“大咪”,修改为“十三”
- (void)viewDidLoad {
[super viewDidLoad];
Person *p = [[Person alloc]init];
p.cat = [Cat new];
p.cat.name = @"大咪";
[p addObserver:self forKeyPath:@"cat.name" options:NSKeyValueObservingOptionNew context:NULL];
_p = p;
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
_p.cat.name = @"十三";
}
这也叫属性的依赖;
5.如何监听类的多个属性呢?
现在,cat类,有多个属性。age,length,weight;
#import <Foundation/Foundation.h>
@interface Cat : NSObject
@property (nonatomic,copy)NSString *name;
@property (nonatomic,assign)int age;
@property (nonatomic,assign)double length;
@property (nonatomic,assign)double weight;
@end
我们想要监听这三个属性,是怎么监听的呢?
- (void)viewDidLoad {
[super viewDidLoad];
Person *p = [[Person alloc]init];
p.cat = [Cat new];
p.cat.name = @"大咪";
p.cat.age = 1;
p.cat.length = 0.3;
p.cat.weight = 5;
[p addObserver:self forKeyPath:@"cat.age" options:NSKeyValueObservingOptionNew context:NULL];
[p addObserver:self forKeyPath:@"cat.length" options:NSKeyValueObservingOptionNew context:NULL];
[p addObserver:self forKeyPath:@"cat.weight" options:NSKeyValueObservingOptionNew context:NULL];
_p = p;
}
是这样吗?三个属性或许还能这样写,10个属性呢?
如果我们要监听某个类(Person)的多个属性,我们可以在这个属性的.m文件里面,重写一个方法;
+ (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
#import "Person.h"
@implementation Person
+ (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key{
NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];
if ([key isEqualToString:@"cat"]) {//判断是否是cat属性
NSArray *arr = @[@"_cat.age",@"_cat.weight"];//想要监听的属性
keyPaths = [keyPaths setByAddingObjectsFromArray:arr];//修改keyPaths
}
return keyPaths;
}
@end
---这样就监听了,cat的age,cat的weight ----
- (void)viewDidLoad {
[super viewDidLoad];
Person *p = [[Person alloc]init];
p.cat = [Cat new];
p.cat.name = @"大咪";
p.cat.age = 1;
p.cat.length = 0.3;
p.cat.weight = 5;
// [p addObserver:self forKeyPath:@"cat.age" options:NSKeyValueObservingOptionNew context:NULL];
// [p addObserver:self forKeyPath:@"cat.length" options:NSKeyValueObservingOptionNew context:NULL];
// [p addObserver:self forKeyPath:@"cat.weight" options:NSKeyValueObservingOptionNew context:NULL];
[p addObserver:self forKeyPath:@"cat" options:NSKeyValueObservingOptionNew context:NULL];
_p = p;
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
NSLog(@"%@",change);
}
//点击屏幕
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
static int a = 0;
static float b = 10;//
static float c = 20;//
_p.cat.age = a;
_p.cat.weight = b;
_p.cat.length = c;
}
开始接触底层
6.尝试监听数组,发现直接无法监听, KVO底层,其实是监听 “Set” 方法!!
例子:我们来尝试监听属性的“NSMutableArray”,属性“arr”
#import <Foundation/Foundation.h>
#import "Cat.h"
@interface Person : NSObject
@property (nonatomic,copy)NSString *name;
@property (nonatomic,assign)int age;
@property (nonatomic,strong)Cat *cat;
@property (nonatomic,strong)NSMutableArray *arr;//新增一个数组属性
@end
#import "ViewController.h"
#import "Person.h"
#import "Cat.h"
@interface ViewController ()
@property (nonatomic,strong)Person *p;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
Person *p = [[Person alloc]init];
p.arr = [NSMutableArray arrayWithObjects:@"Aa",@"Bb", nil];
[p addObserver:self forKeyPath:@"cat" options:NSKeyValueObservingOptionNew context:NULL];
_p = p;
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
NSLog(@"%@",change);
}
//点击屏幕
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[_p.arr addObject:@"Cc"];
[_p.arr addObject:@"Dd"];
}
@end
点击屏幕发现并无打印输出;
点击屏幕,发现无法监听数组变化;
点击屏幕以后发现没有变化,因为,array的add方法,并不走set方法;
如果要观察容器类属性的变化?如何观察呢?
我们要结合一个东西,那就是KVC!
请看第二章节。
关于KVO底层,看它就够了(二:底层解密)
网友评论