一、初步探索
- 简单使用
准备工作
@interface People : NSObject
@property(nonatomic, copy)NSString *name;
@property(nonatomic, copy)NSString *nickName;
@end
static void *PeopleNameContext = "PeopleName";
self.p = [People new];
self.p.name = @"xiaobing";
[self.p addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:PeopleNameContext];
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
if (context == PeopleNameContext){
NSLog(@"------%@",change);
}
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
self.p.name = @"xiaobing11";
}
重点 此处 context 强烈建议写 这样的好处是可以更好的在观察回调中进行区分 代码更易读,执行效率上也会有提高
- 移除观察者
- (void)dealloc{
[self.p removeObserver:self forKeyPath:@"name"];
}
重点 强烈建议要移除观察者 例如当观察对象已经销毁 但是被观察者对象没有销毁 例如单利,这时会出现崩溃。
- 手动观察
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key{
return YES;
}
默认情况下 automaticallyNotifiesObserversForKey 是返回YES 是开启自动观察的 当返回NO时就是手动观察 代码如下
- (void)setName:(NSString *)name{
[self willChangeValueForKey:@"name"];
_name = name;
[self didChangeValueForKey:@"name"];
}
- 多值观察
添加属性
@property(nonatomic, assign)double downloadProgress;
@property(nonatomic, assign)double totalData;
@property(nonatomic, assign)double writeData;
+ (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key{
NSSet *keyPath = [super keyPathsForValuesAffectingValueForKey:key];
if ([key isEqualToString:@"downloadProgress"]){
NSArray *affectingKeys = @[@"totalData",@"writeData"];
keyPath = [keyPath setByAddingObjectsFromArray:affectingKeys];
}
return keyPath;
}
downloadProgress 是依赖 totalData 和 writeData 的
- 可变数组
@property(nonatomic, strong)NSMutableArray *dataArr;
因为KVO是基于KVC的 [self.p.dataArr addObject:@“123”]; 这种没有引起set方法的调用 所以不会调用回调方法
[[self.p mutableArrayValueForKey:@"dataArr"] addObject:@"qwe"];
二、原理探究
KVO是通过生成一个中间类 以 NSKVONotifying 开头 改变被观察对象的isa指针 指向新类 NSKVONotifying_People 断点调试如下结果
- 在[self.p addObserver:self forKeyPath:@“name” options:NSKeyValueObservingOptionNew context:PeopleNameContext];处断点
- 断点前
po object_getClassName(self.p)
“People” - 断点后
po object_getClassName(self.p)
“NSKVONotifying_People” - 通过 po class_getSuperclass(NSClassFromString(@“NSKVONotifying_People”))
结果是 People 得出新生成的类是原有类的子类 - 打印新生成类的子类
unsigned int count = 0;
Method *methods = class_copyMethodList(NSClassFromString(@"NSKVONotifying_People"), &count);
for (int i = 0; i < count; i ++){
NSLog(@"----methods----%@", NSStringFromSelector(method_getName(methods[i])));
}
结果
setDataArr:
setTotalData:
setWriteData:
setDownloadProgress:
setName:
class
dealloc
_isKVOA
中间类重写了属性的set方法
- 生成的中间类是否销毁。不销毁