KVO定义
KVO全称Key Value Observing,根据runtime实现的,是观察者设计模式的一种实现,由NSKeyValueObserving协议提供支持,NSObject类实现了该协议,因此所有NSObject的子类都可以使用KVO
KVO使用
使用三部曲:
- 注册监听
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
observer:观察者
keyPath:观察的属性路径
options:观察值的类型 NSKeyValueObservingOptionNew-新值 NSKeyValueObservingOptionOld-老值
content:上下文,快速定位观察属性
- 实现监听代理
- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context;
keyPath:观察的属性路径
content:上下文
change中包括的值
kind:
//set方法
NSKeyValueChangeSetting = 1,
//插入
NSKeyValueChangeInsertion = 2,
//删除
NSKeyValueChangeRemoval = 3,
//替换
NSKeyValueChangeReplacement = 4,复制代码
new:新值 观察属性时options设置了NSKeyValueObservingOptionNew,会返回
old:旧值 观察属性时options设置了NSKeyValueObservingOptionOld,会返回
- 移除监听
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
注:一定要移除,不然会出现莫名的崩溃问题
手动观察\自动观察
重写
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key;
默认返回YES,返回NO则不自动观察属性,需要观察属性需要在对应setter方法设置值的前后,执行
- (void)willChangeValueForKey:(NSString *)key;
- (void)didChangeValueForKey:(NSString *)key;
例:
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
if ([key isEqualToString:@"name"]) {
return NO;
}
return YES;
}
- (void)setName:(NSString *)name {
//手动添加观察者
[self willChangeValueForKey:@"name"];
_name = name;
[self didChangeValueForKey:@"name"];
}
多属性决定一个属性的值
实现
+ (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
例:
+ (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key {
NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];
if ([key isEqualToString:@"proportion"]) {
NSArray *affectingKeys = @[@"height", @"width"];
keyPaths = [keyPaths setByAddingObjectsFromArray:affectingKeys];
}
return keyPaths;
}
- (CGFloat)proportion {
return self.width/self.height;
}
集合属性的监听
例:
//LAnimal.h
@property (nonatomic, strong) NSMutableArray *actions;
//ViewController.h
@property (nonatomic, retain) LAnimal *animal;
//ViewController.m/viewDidLoad
[self.animal addObserver:self forKeyPath:@"actions" options:(NSKeyValueObservingOptionNew) context:NULL];
//使用此方法集合属性的无法监听
[self.animal.actions addObject:@"fly"];
//需要使用KVC 集合的setter方法
[[self.animal mutableArrayValueForKey:@"actions"] addObject:@"run"];//kind:2
[[self.animal mutableArrayValueForKey:@"actions"] removeLastObject];//kind:3
[self.animal mutableArrayValueForKey:@"actions"][0] = @"eat";//kind:4
KVO原理探索
- KVO观察的是setter方法
通过监听实例变量与属性
//LAnimal.h
{
@public
NSString *secondName;
}
@property (nonatomic, copy) NSString *name;
//ViewController.m/viewDidLoad
[self.animal addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:@"animal_name"];
[self.animal addObserver:self forKeyPath:@"secondName" options:NSKeyValueObservingOptionNew context:@"second_name"];
//然后设置值
self.animal.name = @"Lucy";
self.animal->secondName = @"haha";
通过上面代码,可以发现实例变量并没有被KVO监听,而属性是被监听的,推断KVO监听的处理可能是在setter方法
- 在被监听后,类前后的变化
//ViewController.m
- (void)catClasses: (Class)cls {
int count = objc_getClassList(NULL, 0);
NSMutableArray *tmpAry = [NSMutableArray arrayWithObject:cls];
Class *clses = (Class *)malloc(sizeof(Class)*count);
objc_getClassList(clses, count);
for (int i=0; i < count; i++) {
if (cls == class_getSuperclass(clses[i])) {
[tmpAry addObject:clses[i]];
}
}
free(clses);
NSLog(@"%s\n%@",__FUNCTION__, tmpAry);
}
//ViewController.m/viewDidLoad
[self catClasses:[LAnimal class]];
[self.animal addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:@"animal_name"];
[self.animal addObserver:self forKeyPath:@"secondName" options:NSKeyValueObservingOptionNew context:@"second_name"];
NSLog(@"************************");
[self catClasses:[LAnimal class]];
打印信息:
2019-12-25 16:53:25.960926+0800 KVODemo[38333:4314339] -[ViewController catClasses:]
(
LAnimal
)
2019-12-25 16:53:32.088372+0800 KVODemo[38333:4314339] ************************
2019-12-25 16:53:32.093974+0800 KVODemo[38333:4314339] -[ViewController catClasses:]
(
LAnimal,
"NSKVONotifying_LAnimal"
)
通过打印信息可以知道属性被监听后,会重新生成LAnimal的一个子类NSKVONotifying_LAnimal
- 查看生成的子类哪些方法发生了变化
self.animal = [LAnimal new];
[self.animal addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:@"animal_name"];
NSLog(@"************************");
[self catMethods:NSClassFromString(@"NSKVONotifying_LAnimal")];
打印信息:
2019-12-25 17:06:44.514170+0800 KVODemo[38621:4331805] .cxx_destruct----0x10e85e4d0
2019-12-25 17:06:44.514424+0800 KVODemo[38621:4331805] name----0x10e85e460
2019-12-25 17:06:44.514507+0800 KVODemo[38621:4331805] setName:----0x10e85e490
2019-12-25 17:06:48.723917+0800 KVODemo[38621:4331805] ************************
2019-12-25 17:06:50.295125+0800 KVODemo[38621:4331805] setName:----0x10ebb8b5e
2019-12-25 17:06:50.295294+0800 KVODemo[38621:4331805] class----0x10ebb7592
2019-12-25 17:06:50.295430+0800 KVODemo[38621:4331805] dealloc----0x10ebb7336
2019-12-25 17:06:50.295503+0800 KVODemo[38621:4331805] _isKVOA----0x10ebb732e
通过打印信息可以看到setName方法imp发生了变变化即方法被重写了,并新增了class、dealloc、_isKVOA三个方法,到这可以验证1中的猜测
原理概述:
当类的对象被第一次观察时,会在运行期动态生成一个该类的派生类,并在这个派生类中重写被观察属性的setter方法,通知机制就是在这个setter方法中实现的,例:原类A -> NSKVONotifying_A,每一个对象都有一个isa指针指向当前类,当这个类被观察时,isa指针就会指向新的派生类,被观察属性被赋值时执行的是新类的setter方法,键值观察的通知主要依赖于NSObject的两个方法,willChangeValueForKey和didChangValueForKey,前者是被观察属性发生变化前调用记录就旧值,改变之后后者被调用,最后会调用observeValueForKeyPath代理方法
生活如此美好,今天就点到为止。。。
网友评论