了解更多iOS底层原理知识,关注腾讯课堂 金服学院 iOS高级开发
上一篇讲了KVC,那么KVO是Cocoa提供的一种基于KVC的机制,允许一个对象(A)去监听另一个对象(B)的某个属性,当该属性改变时,系统会通知监听的对象(A)
请注意,这里的刚描述的通知和IOS系统自带NSNotificationCenter是两回事,后续会写篇NSNotification,就能理解是两码事。
先了解KVO的使用,再来逐步分析
一、KVO的基本使用流程有三步
1添加监听
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullablevoid *)context;(系统还有其他添加方法)
2接收通知
- (void)observeValueForKeyPath:(nullableNSString *)keyPath ofObject:(nullableid)object change:(nullableNSDictionary<NSKeyValueChangeKey,id> *)change context:(nullablevoid *)context
3移除监听
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context (系统还有其他移除方法)
上面1添加监听和3移除监听的方法中,observer是监听者即上文的对象(B),keyPath是被监听对象的属性即上文对象(A)的属性,context是个可选参数,可为空,和2接收通知方法中的context是同一个,可以用来区分不同的通知。
注意:1添加监听方法中的options的配置和2接收通知方法中的change值有关联,其中options取值有4个,可以用|或运算连接
NSKeyValueObservingOptionNew//2接收通知方法中的change字典中包含改变后的新值
NSKeyValueObservingOptionOld//2接收通知方法中的change字典中包含改变前的旧值
NSKeyValueObservingOptionInitial//2接收通知方法中的change字典中包含改变后的新值,但对象在添加监听(addObserver)的时候,会触发一次通知(即2接收通知方法)
NSKeyValueObservingOptionPrior//监听属性在改变前发送一次通知,改变后发送一次通知
KVO的流程和参数说明就简单的介绍到这里。
二、接下来了解KVO通知的触发方式
1 自动触发
2 手动触发
系统会通过+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key方法来判断手动和自动,返回YES是自动,NO为手动。
手动调用的方式是结合willChangeValueForKey和didChangeValueForKey方法使用,代码如下
[personwillChangeValueForKey:@"name"];
person.name =@"八点钟";
[persondidChangeValueForKey:@"name"];
那么我们自动触发,其实就是系统底层自动调用了willChangeValueForKey和didChangeValueForKey这两个方法。
从KVO流程中,我们看到,一切的变化都在1添加监听后发生了变化,那么addObserver这个方法里面底层做了些什么呢?
在当对象(B)被监听时,那么系统就会在运行期动态的创建该对象类的一个子类,类名就是在该类的前面加上NSKVONotifying_的前缀,子类并重写了任何被监听属性的setter方法,并使用willChangeValueForKey和didChangeValueForKey即手动触发方式来实现,这么做是基于设置属性会调用setter方法(KVC协议)。
并且系统将这个被监听的对象(B)的isa指针指向新生成的子类,那么这个对象其实就成为该子类的对象了。
解释为什么被监听对象(B)怎么就变成了一个子类对象,看NSObject结构
@interface NSObject <NSObject> {
Class isa OBJC_ISA_AVAILABILITY;
}
发现NSObject对象其实就是维护一个isa指针,可以这么理解,NSObject其实就是一个驱壳,而真正的控制这个类的是isa指针。
好,我们直接上代码看addObserver方法前后对象person变化的结果,代码如下:
NSLog(@"before:%@", [personclass]);
NSLog(@"before:%@",object_getClass(person));
[personaddObserver:selfforKeyPath:@"name"options:NSKeyValueObservingOptionNewcontext:nil];
NSLog(@"after:%@", [personclass]);
NSLog(@"after:%@",object_getClass(person));
日志log结果:
注意:如果你的属性监听是手动开启,即automaticallyNotifiesObserversForKey方法返回是NO,看到的日志结果都是Person类。
我们已经看到了前后的变化,那么继续看子类变化,子类其实是在操作了addObserver生成的,代码如下:
NSLog(@"before:%@", [PersonfindAllOf:[Personclass]]);
[personaddObserver:selfforKeyPath:@"name"options:NSKeyValueObservingOptionNewcontext:nil];
NSLog(@"after:%@", [PersonfindAllOf:[Personclass]]);
日志log结果:
findAllOf:代码方法如下:
+ (NSArray *)findAllOf:(Class)defaultClass
{
int count =objc_getClassList(NULL,0);
if (count <=0){
return [NSArrayarrayWithObject:defaultClass];
}
NSMutableArray *output = [NSMutableArrayarrayWithObject:defaultClass];
Class *classes = (Class *) malloc(sizeof(Class) * count);
objc_getClassList(classes, count);
for (int i =0; i < count; ++i) {
if (defaultClass ==class_getSuperclass(classes[i])){//子类
[output addObject:classes[i]];
}
}
free(classes);
return [NSArrayarrayWithArray:output];
}
补充一下,重写willChangeValueForKey和didChangeValueForKey这两个方法,填上日志,可以看到KVO通知自动触发方式调用了这两个方法,代码如下:
- (void)willChangeValueForKey:(NSString *)key
{
NSLog(@"%s",__func__);
NSLog(@"%@", [selfclass]);
NSLog(@"%@",object_getClass(self));
[superwillChangeValueForKey:key];
}
- (void)didChangeValueForKey:(NSString *)key
{
NSLog(@"%s",__func__);
[superdidChangeValueForKey:key];
}
日志Log结果:
从日志中也可以看出真正调用willChangeValueForKey:和didChangeValueForKey:的是NSKVONotifying_Person子类对象。
看到这里,结合上篇,那么KVO和KVC的关系就很明了了,当被监听的对象属性值发生变化,值的变化是通过KVC方法来实现的,而KVO重写了KVC的方法,从而到达了一个监听的目的。
下篇:NSNotification深度解析