1.KVO介绍
KVO是键值观察``key value observing的简称,在iOS开发中,可使用KVO来监听一个对象属性的变化。
2.KVO的使用
2.1.对某个类的属性添加监听
[self.person addObserver:self forKeyPath:@"nick" options:NSKeyValueObservingOptionNew context:NULL];
self.person是LGPerson的实例对象;- 给
LGPerson的实例对象self.person添加观察者self,监听self.person的属性nick的新值变化(NSKeyValueObservingOptionNew).
2.2.观察者的回调方法
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
NSLog(@"%@",change);
}
- 当被观察者的属性变化时,会来到这个回调方法,keyPath是被监听的属性,object是被观察的对象,change是数据的变化。
2.3.移除观察者
- (void)dealloc{
[self.person removeObserver:self forKeyPath:@"nick" context:NULL];
}
移除
self对self.person对属性nick的监听; 必须要移除,不然会崩溃。
2.4.数组类型的属性
self.person.dateArray = [NSMutableArray arrayWithCapacity:1];
[self.person addObserver:self forKeyPath:@"dateArray" options:(NSKeyValueObservingOptionNew) context:NULL];
[[self.person mutableArrayValueForKey:@"dateArray"] addObject:@"1"];
对象的属性--数组,需要使用KVC的形式赋值才能监听
关闭KVO
+ (BOOL) automaticallyNotifiesObserversForKey:(NSString *)key{
return YES;
}
当上面的方法返回NO时,整个类的监听都会被关闭。
也可以只关闭某个属性的监听,这里以nick属性为例
+(BOOL)automaticallyNotifiesObserversOfNick
{
return NO;
}
3.KVO的底层原理
3.1.添加监听时的处理
实现如下方法,打印类及其子类
#pragma mark - 遍历类以及子类
- (void)printClasses:(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(@"classes = %@", mArray);
}
实现如下方法打印类的方法列表
#pragma mark - 遍历类的方法列表
- (void)printClassAllMethod:(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(@"%@-%p",NSStringFromSelector(sel),imp);
}
free(methodList);
}
通过监听前后对
LGPerson及其子类列表的打印,我们发现,当对LGPerson的对象self.person进行监听后,系统自动生成了一个LGPerson的子类NSKVONotifying_LGPerson;
再来看看NSKVONotifying_LGPerson的方法列表
可以看到,
NSKVONotifying_LGPerson中重写了父类的setNickName:方法
再来看self.person的isa指向
可以看到
self.person的isa指向,由LGPerson变成了NSKVONotifying_LGPerson.
通过上面的分析我们可以得出:当对一个对象的属性进行监听时,会生成一个当前对象所属类的子类NSKVONotifing_ClassName,并将当前对象的isa指针指向新创建的这个派生类。在这个派生类中重写了当前对象所监听的属性的setter方法。在这个派生类中还重写了class方法,当调用[self.person class]时,返回的是LGPerson,还实现了一个isKVO方法,标记当前类是实现监听的派生类.
3.2.销毁监听时的处理
- 可以看到,销毁监听后,派生类
NSKVONotifying_LGPerson依然存在,这样设计可以方便下次监听时不用重复创建了;- 销毁监听后对象的
isa指针指回了LGPerson;
当对一个对象属性的监听被移除后,对象的isa指针会重新指向原来的类,保证了不会出现混乱,这也体现了结束监听时销毁监听的重要性。