KVO简介
KVO概述
- KVO是键值观察者(key-value-observing)
- KVO提供一种观察者机制,通过对某个属性添加添加观察者,当值改变时,就会调用”observeValueForKeyPath:”方法,为我们提供一个“对象值改变了!”的时机进行一些操作
- KVO 是一个观察者模式。观察一个对象的属性,注册一个指定的路径,若这个对象的的属性被修改,则 KVO 会自动通知观察者
- Objective-C 中有两种使用键值观察的方式:手动或自动,此外还支持注册依赖键(即一个键依赖于其他键,其他键的变化也会作用到该键
KVO的基本使用
- 注册观察者,实施监听
HTPerson *p = [HTPerson new];
self.p = p;
[self.p addObserver:**self** forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:@"name改变了"];
复制代码
- 回调方法
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
复制代码
- 移除观察者
[self removeObserver:**self** forKeyPath:@"name"];
复制代码
KVO的使用场景
- 下拉刷新、下拉加载监听UIScrollView的contentoffsize;
- webview混排监听contentsize;
- 监听模型属性实时更新UI;
- 监听控制器frame改变,实现抽屉效果。
键值观察
设置属性
将观察者与被观察者注册好之后,就可以对观察者对象的属性进行操作,这些变更操作就会被通知给观察者对象。注意,只有遵循 KVO 方式来设置属性,观察者对象才会获取通知,也就是说遵循使用属性的 setter 方法,或通过key-path
来设置:
[target setAge:30];
[target setValue:[NSNumber numberWithInt:30] forKey:@"age"];
[[self.p mutableArrayValueForKeyPath:@"moneys"]addObject:@"哈哈"];
复制代码
处理变更通知
观察者需要实现名为 NSKeyValueObserving
的 category 方法来处理收到的变更通知:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context;
复制代码
在这里,change 这个字典保存了变更信息,具体是哪些信息取决于注册时的 NSKeyValueObservingOptions。
手动关闭和自动关闭监听
系统默认automaticallyNotifiesObserversForKey返回YES
,如果设置成NO
则监听回调不会执行,可以对单个属性进行关闭,也可以在automaticallyNotifiesObserversOfName
返回NO
关闭name
的监听
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key{
return NO;
}
+ (BOOL)automaticallyNotifiesObserversOfName{
return NO;
}
复制代码
KVO原理
- 当某个类的对象第一次被观察时,系统就会在运行期动态地创建该类的一个
派生类
- 在这个派生类中
重写
基类中任何被观察属性的 setter 方法。 - 派生类在被重写的 setter 方法实现真正的通知机制,就如前面手动实现键值观察那样。
- 基于设置属性会调用 setter 方法,而通过重写就获得了 KVO 需要的通知机制。
- 前提是要通过遵循 KVO 的属性设置方式来变更属性值,如果仅是直接修改属性对应的成员变量,是无法实现 KVO 的。
- 同时派生类还
重写
了class
方法以“欺骗”外部调用者它就是起初的那个类。 - 系统将这个对象的
isa
指针指向这个新诞生的派生类,因此这个对象就成为该派生类的对象了,因而在该对象上对 setter 的调用就会调用重写的 setter,从而激活键值通知机制。 - 派生类还
重写
了dealloc
方法来释放资源。 - 当一个观察者注册对象的一个属性 isa 观察对象的指针被修改,指着一个中间类而不是在真正的类。
- isa 指针的作用:每个对象都有 isa 指针,指向该对象的类,它告诉 runtime 系统这个对象的类是什么
KVO实现过程
实现代码如下所示
- (void)viewDidLoad {
[super viewDidLoad];
HTPerson *p = [HTPerson new];
_p = p;
//输出类和子类
[self printClass:[p class]];
//输出方法列表
[self printClassMethod:objc_getClass("HTPerson")];
//监听
[p addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:@"name改变了"];
//输出类和子类
[self printClass:[p class]];
//输出方法列表
[self printClassMethod:objc_getClass("NSKVONotifying_HTPerson")];
//监听
[p addObserver:self forKeyPath:@"moneys" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:@"array改变了"];
//输出类和子类
[self printClass:[p class]];
//输出方法列表
[self printClassMethod:objc_getClass("NSKVONotifying_HTPerson")];
NSTimer *timer = [NSTimer timerWithTimeInterval:1 target:self selector: @selector(timerAction) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop]addTimer:timer forMode:NSRunLoopCommonModes];
[timer fire];
_timer = timer;
}
//便利类和子类
- (void)printClass:(Class)cls{
//注册类的总数
int count = objc_getClassList(NULL, 0);
//创建一个数组,其中包含特定对象
NSMutableArray *mArray = [NSMutableArray arrayWithObject:cls];
//获取所有已经注册的类
Class *classes = (Class *)malloc(**sizeof**(Class)*count);
//
objc_getClassList(classes, count);
for (int i = 0; i<count; i++) {
if (cls == class_getSuperclass(classes[i])) {
[mArray addObject:classes[i]];
}
}
free(classes);
NSLog(@"%@",mArray);
}
//输入类的方法
- (void)printClassMethod:(Class)cls{
unsigned int count = 0;
Method *methodList = class_copyMethodList(cls, &count);
for (int i = 0; i<count; i++) {
Method method = methodList[i];
SEL sel = method_getName(method);
// IMP imp = class_getMethodImplementation(cls, sel);
NSLog(@"%@",NSStringFromSelector(sel));
}
free(methodList);
}
- (void)timerAction{
self.p.name = [NSString stringWithFormat:@"%@$",self.p.name];
[[self.p mutableArrayValueForKeyPath:@"moneys"]addObject:@"哈哈"];
if (self.p.name.length>10) {
[self.timer invalidate];
self.timer = nil;
[self.p removeObserver:self forKeyPath:@"name"];
[self.p removeObserver:self forKeyPath:@"moneys"];
//输出类和子类
[self printClass:objc_getClass("NSKVONotifying_HTPerson")];
}
}
\
-(**void**)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
/*
* keyPath : 监听的属性
* object : 监听的哪个对象
* change : 根据监听的结构体,展示监听的内容
* context : 监听传入的context
*/
NSLog(@"keyPath:%@ object:%@ context:%@ change:%@",keyPath,object,context,change);
}
复制代码
- 添加监听,会派生一个新的子类
NSKVONotifying_HTPerson
,self.p的isa指向该类。
- 同时会重写其部分方法:setName,class,dealloc,_isKVOA
- 在移除监听后,self.p的isa指向不再是派生类,是类
参考
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END