KVO的调用分为自动调用和手动调用,一般的使用自动调用比较多。下面先说说自动调用。
一、自动调用
准备工作:
1、定义一个model,代码如下:
@interface ZPZPersonModel : NSObject
@property (nonatomic, copy) NSString * name;
@property (nonatomic, assign) NSInteger age;
@property (nonatomic, copy) NSString * ID;
@property (nonatomic, assign) CGFloat height;
@end
2、在vc中添加观察者
- (void)addKVOForModel {
_personModel = [[ZPZPersonModel alloc] init];
[_personModel addObserver:self forKeyPath:NSStringFromSelector(@selector(name)) options:NSKeyValueObservingOptionNew context:&personContext];
[_personModel addObserver:self forKeyPath:NSStringFromSelector(@selector(age)) options:NSKeyValueObservingOptionOld context:&personContext];
[_personModel addObserver:self forKeyPath:NSStringFromSelector(@selector(ID)) options:NSKeyValueObservingOptionInitial context:&personContext];
[_personModel addObserver:self forKeyPath:NSStringFromSelector(@selector(height)) options:NSKeyValueObservingOptionPrior context:&personContext];
NSLog(@"before:%p",_personModel);
}
3、回调
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
NSLog(@"%@--%@",keyPath,change);
}
1、参数的了解
使用如下代码添加观察者:
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
- observer:谁来观察该对象的属性和方法
- keyPath:要观察的对象的属性和方法,一般使用NSStringFromSelector(@selector(selector))来获取
- options:决定观察的类型,不同的类型,触发的回调的内容和时机不同,后面会着重说
- context:随机值
options
typedef NS_OPTIONS(NSUInteger, NSKeyValueObservingOptions) {
NSKeyValueObservingOptionNew = 0x01,
NSKeyValueObservingOptionOld = 0x02,
NSKeyValueObservingOptionInitial API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0)) = 0x04,
NSKeyValueObservingOptionPrior API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0)) = 0x08
};
使用方法:可以任意多个组合,比如任意两个、三个、四个组合
注意点:
- NSKeyValueObservingOptionNew:在添加了观察者后并赋值了会有回调,返回NSKeyValueChangeKindKey和NSKeyValueChangeNewKey
- NSKeyValueObservingOptionOld:在添加了观察者后并赋值了会有回调,返回NSKeyValueChangeKindKey和NSKeyValueChangeOldKey
- NSKeyValueObservingOptionInitial:添加观察者时就触发回调,并且在后面赋值时也会触发回调,但是都只返回NSKeyValueChangeKindKey
- NSKeyValueObservingOptionPrior:在添加了观察者后并赋值了会有回调,但是会回调两次,第一次返回NSKeyValueChangeKindKey、NSKeyValueChangeNotificationIsPriorKey,第二次回调只返回NSKeyValueChangeKindKey
- 组合调用1:NSKeyValueObservingOptionInitial只有在和NSKeyValueObservingOptionNew搭配的时候,才会返回NSKeyValueChangeNewKey,和其他的搭配都只返回NSKeyValueChangeKindKey
- 组合调用2:NSKeyValueObservingOptionPrior和NSKeyValueObservingOptionNew或者NSKeyValueObservingOptionOld搭配时,只会在第二次回调时返回NSKeyValueChangeNewKey或者NSKeyValueChangeOldKey
总结:
1、从上面可以看出,只有options为NSKeyValueObservingOptionNew和NSKeyValueObservingOptionOld会有值的输出
2、同一个对象的keyPath不要添加多次,否则会执行多次,如下面的代码就会执行两次,调用顺序是按照先进后出的顺序:
[_personModel addObserver:self forKeyPath:NSStringFromSelector(@selector(ID)) options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial context:&personContext];
[_personModel addObserver:self forKeyPath:NSStringFromSelector(@selector(ID)) options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial context:&personContext];
2、正常对象调用
上面的例子就是,这里不再赘述
3、NSArray和NSSet添加观察者(看后面)
二、手动调用
这里以给对象_personModel的属性name添加观察者。
1、必须要在监控的对象里重写一些方法(有两种重写方式)
- 直接重写+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key方法
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
if ([key isEqualToString:@"name"]) {
return NO;
}
return [super automaticallyNotifiesObserversForKey:key];
}
- 每个属性会生成相应的可用来设置是否手动触发观察者的方法,只限于属性,方法是不会生成的,比如name会生成+ (BOOL)automaticallyNotifiesObserversOfName方法:
+ (BOOL)automaticallyNotifiesObserversOfName {
return NO;
}
如果上述两个方法都出现了,则第一个的优先级高于第二个,如下所示:
+ (BOOL)automaticallyNotifiesObserversOfName {
return NO;
}
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
if ([key isEqualToString:@"name"] || [key isEqualToString:@"ID"]) {
return NO;
}
return [super automaticallyNotifiesObserversForKey:key];
}
不会再去调用第一个方法!
2、写法(有两种)
- 第一种:直接写在属性的set方法里,如下:
- (void)setName:(NSString *)name {
[self willChangeValueForKey:@"name"];
_name = name;
[self didChangeValueForKey:@"name"];
}
- 第二种:写在要改变值的地方(这里以ID为观察对象),如下:
- (void)givePersonName {
_personModel.name = @"new name";
[_personModel willChangeValueForKey:@"ID"];
_personModel.ID = @"999";
[_personModel didChangeValueForKey:@"ID"];
[_personModel addName:@"zhou" andID:@"999"];
}
3、添加观察者
和自动调用一致,添加都是一样的:
- (void)addKVOForModel {
_personModel = [[ZPZPersonModel alloc] init];
[_personModel addObserver:self forKeyPath:NSStringFromSelector(@selector(name)) options:NSKeyValueObservingOptionNew context:NULL];
[_personModel addObserver:self forKeyPath:NSStringFromSelector(@selector(ID)) options:NSKeyValueObservingOptionNew context:NULL];
[_personModel addObserver:self forKeyPath:NSStringFromSelector(@selector(addName:andID:)) options:NSKeyValueObservingOptionNew context:NULL];
}
既然是Key-Value观察,那么对方法是不起作用的!!!
三、总结
1、通过断点会发现,观察者对于同一个属性的添加只会添加一次,即下面的代码只会调用一次,这是为什么?
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
if ([key isEqualToString:@"name"] || [key isEqualToString:@"ID"]) {
return NO;
}
BOOL result = [super automaticallyNotifiesObserversForKey:key];
return result;
}
2、方法+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key 在调用 [_personModel addObserver:self forKeyPath:NSStringFromSelector(@selector(name)) options:NSKeyValueObservingOptionNew context:NULL];时调用,可以猜测,在该方法里自动调用了+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key。
以上只是对KVO的简单了解和简单使用,接下来将会着重研究其原理。
代码在这里
网友评论