最好的学习文档是 KVO苹果官方文档
KVO三步曲
- 添加观察者
- (void)addObserver:(NSObject *)observer
forKeyPath:(NSString *)keyPath
options:(NSKeyValueObservingOptions)options
context:(nullable void *)context;
-
observer:
注册KVO通知的对象。观察者必须实现key-value observing
方法observeValueForKeyPath:ofObject:change:context:
。 -
keyPath:
观察者的属性的keyPath
,相对于接受者,值不能为nil
。 -
options:``NSKeyValueObservingOptions
的组合,它指定了观察通知中包含了什么,可以查看“NSKeyValueObservingOptions”
。 -
context:
标签,区分多监听或继承情况
- 响应观察
- (void)observeValueForKeyPath:(nullable NSString *)keyPath
ofObject:(nullable id)object
change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change
context:(nullable void *)context;
- 移除观察
- (void)removeObserver:(NSObject *)observer
forKeyPath:(NSString *)keyPath
context:(nullable void *)context;
- (void)removeObserver:(NSObject *)observer
forKeyPath:(NSString *)keyPath;
KVO使用
- 示例一个SPPerson类
@interface SPPerson : 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 *dataArray;
@end
- SPStudent继承自SPPerson
@interface SPStudent : SPPerson<NSCopying>
// 什么也没做
@end
- 注册观察者
static void *PersonNameContext = &PersonNameContext;
static void *StudentNameContext = &StudentNameContext;
// 注册观察者
[self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:PersonNameContext];
[self.person addObserver:self forKeyPath:@"age" options:(NSKeyValueObservingOptionNew) context:NULL];
[self.student addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:StudentNameContext];
[self.person addObserver:self forKeyPath:@"dataArray" options:NSKeyValueObservingOptionNew context:NULL];
- 响应观察者回调方法
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
NSLog(@"%@ -- %@", change, object);
}
- 发送新值 setter方法
self.person.name = @"spirej";
self.student.name = @"sp_spirej";
// 集合,取值和赋值过程和普通类型不一样
[[self.person mutableArrayValueForKey:@"dataArray"] addObject:@"joo"];
- 移除观察者
- (void)dealloc {
[self.person removeObserver:self forKeyPath:@"name" context:PersonNameContext];
[self.person removeObserver:self forKeyPath:@"age"];
[self.student removeObserver:self forKeyPath:@"name" context:StudentNameContext];
[self.person removeObserver:self forKeyPath:@"dataArray"];
}
打印结果:
2019-11-12 18:54:30.748597+0800 KVO[27143:3715662] {
kind = 1;
new = spirej;
} -- <SPPerson: 0x6000008a65c0>
2019-11-12 18:54:30.749288+0800 KVO[27143:3715662] {
kind = 1;
new = "sp_spirej";
} -- <SPStudent: 0x6000008a6340>
2019-11-12 18:54:30.752466+0800 KVO[27143:3715662] {
indexes = "<_NSCachedIndexSet: 0x600001dd7280>[number of indexes: 1 (in 1 ranges), indexes: (1)]";
kind = 2;
new = (
joo
);
} -- <SPPerson: 0x6000008a65c0>
KVO自动观察和手动观察
- 自动观察
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
return YES;
}
默认返回YES开启自动观察,亦可根据key值调整
- 手动观察
- (void)setName:(NSString *)name {
[self willChangeValueForKey:@"name"];
_name = name;
[self didChangeValueForKey:@"name"];
}
在setter方法属性赋值前后调用willChangeValueForKey 和 didChangeValueForKey 即可开启手动观察,注意:如果自动观察没有关闭,则会观察两次
KVO 的 context
注册观察者方法和响应回调方法中的context,是标签作用,可用来区分同一个属性多监听的情况或继承关系中观察同一个属性的情况
static void *PersonNameContext = &PersonNameContext;
static void *StudentNameContext = &StudentNameContext;
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
if (context == PersonNameContext) {
// ...
}else if (context == StudentNameContext) {
// ...
}else if (context == PersonNickContext) {
// ...
}
NSLog(@"%@ -- %@", change, object);
}
KVO路径集合
当观察对象受多个因素影响时,可以keyPathsForValuesAffectingValueForKey方法把多个因素捆绑起来
例如我这里要观察下载进度,又假如下载进度受已下载数据和总数据影响,那么就可以把已下载进度和总进度捆绑为观察路劲集合
+ (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key{
NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];
if ([key isEqualToString:@"downloadProgress"]) {
NSArray *affectingKeys = @[@"totalData", @"writtenData"];
keyPaths = [keyPaths setByAddingObjectsFromArray:affectingKeys];
}
return keyPaths;
}
使用观察:
[self.person addObserver:self forKeyPath:@"downloadProgress" options:(NSKeyValueObservingOptionNew) context:NULL];
网友评论