近两天看视频教程,粗略的学习了一下KVO的使用。做个笔记,以便后期自己查阅。
KVO的实质则是通过iOS的runtime来动态的创建监听类的子类,重写子类对象的setter方法。在setter方法中发送通知。
KVO的使用
//添加观察者
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
//移除观察者
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context
这两个方法一定是成对出现的。添加后,在dealloc中要将添加的观察者移除。
简述下添加方法的参数含义:
1.observer代表的是监听者。
2.keyPath指的是监听属性的名称。
3.options有四种
- NSKeyValueObservingOptionNew 传回新值。
- NSKeyValueObservingOptionOld 传回旧值。
- NSKeyValueObservingOptionInitial 注册的时候发送一次通知,监听对象的值发生改变的时候也会发送一次通知。
- NSKeyValueObservingOptionPrior 监听的对象值发生改变之前发一次,改变之后发一次。
KVO的手动和自动发送通知方法
这个方法写在监听类中。在这个方法里我们可以根据监听对象的key值来判定是要返回YES还是NO。默认为YES。
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key
KVO的属性依赖
例如我们监听一个Person类,Person类里面有个Dog类的对象dog。Dog有age和level两个属性。那现在我们要如何监听dog里面的属性的变化呢?
首先我们在控制器中添加观察者
[_person addObserver:self forKeyPath:@"dog" options:NSKeyValueObservingOptionNew context:nil]
在Person类中重写属性依赖的方法
+ (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key {
NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];
if ([key isEqualToString:@"dog"]) {
keyPaths = [[NSSet alloc] initWithObjects:@"_dog.age",@"_dog.level", nil];
}
return keyPaths;
}
这样子我们就完成了对于Person类中dog属性变化的监听。
KVO的实质及自定义KVO
KVO的实质
那么最开始我们讲到,KVO的实质就是通过iOS的runtime来动态创建监听类的子类,然后重写子类的setter方法,这边我们再详细的说明一下。
首先我们来看一下,Person类在添加KVO前后的isa指针的变化。

)
再来看一下添加_person添加KVO之后的isa指针是指向什么。

通过两张图我们很明显的看到,_person对象在添加KVO前后的isa指针指向是不同的,这是怎么回事呢?道理很简单,在_person添加KVO的时候,系统就会通过runtime动态的去创建一个Person的子类,也就是NSKVONotifying_Person这个类,也同时的改变的_person的isa指针指向。
自定义KVO
这边自己按照视频教程写了一个自定义的KVO。创建一个NSObject的分类,在分类里面自定义一个添加KVO的方法。
- (void)Leaf_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context {
//1.自定义NSKVONotifying_Person子类
NSString *oldClassName = NSStringFromClass(self.class);
NSString *newClassName = [NSString stringWithFormat:@"LeafKVONotifying_%@",oldClassName];
//创建一个类
Class myClass = objc_allocateClassPair(self.class, newClassName.UTF8String, 0);
//注册创建的类
objc_registerClassPair(myClass);
//2.动态的修改调用者的类型
object_setClass(self, myClass);
//3.添加setName方法 重写父类的setName
class_addMethod(myClass, @selector(setName:), (IMP)setName, "V@:@");
}
void setName(id self, SEL _cmd, NSString *nerName) {
//修改name属性
//通知外界 willChange didChange
}
最终重写setter的方法里面没有完全的实现。后续会补充完整。这里面就是利用消息机制给这个方法发送通知,这样我们就能在这个方法里面收到属性变化后的通知。
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
总结
以上就是我自己对于KVO学习之后的一些理解。后续还有一些关于KVO对于容器类监听的理解,会慢慢补充。还有很多不足之处,一起学习,共勉。
网友评论