设为首页收藏本站
查看: 385|回复: 0

[业界探讨] KVO底层原理分析

[复制链接]

75

主题

75

帖子

442

积分

网站编辑

Rank: 8Rank: 8

积分
442
发表于 2017-1-20 10:21:52 | 显示全部楼层 |阅读模式
本帖最后由 疯狂IT人 于 2017-1-20 10:33 编辑

上一篇讲了KVC,那么KVO是Cocoa提供的一种基于KVC的机制,允许一个对象(A)去监听另一个对象(B)的某个属性,当该属性改变时,系统会通知监听的对象(A)

请注意,这里的刚描述的通知和IOS系统自带NSNotificationCenter是两回事,后续会写篇NSNotification,就能理解是两码事。

先了解KVO的使用,再来逐步分析

一、KVO的基本使用流程有三步

1添加监听


  1. - (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullablevoid *)context;(系统还有其他添加方法)
复制代码

2接收通知

  1. - (void)observeValueForKeyPath:(nullableNSString *)keyPath ofObject:(nullableid)object change:(nullableNSDictionary<NSKeyValueChangeKey,id> *)change context:(nullablevoid *)context
  2. 3移除监听
  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 手动触发
系统会通过
  1. + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key方法来判断手动和自动,返回YES是自动,NO为手动。
  2. 手动调用的方式是结合willChangeValueForKey和didChangeValueForKey方法使用,代码如下
  3. [personwillChangeValueForKey:@"name"];
  4. person.name =@"八点钟";
  5. [persondidChangeValueForKey:@"name"];
复制代码

那么我们自动触发,其实就是系统底层自动调用了willChangeValueForKey和didChangeValueForKey这两个方法。

从KVO流程中,我们看到,一切的变化都在1添加监听后发生了变化,那么addObserver这个方法里面底层做了些什么呢?
在当对象(B)被监听时,那么系统就会在运行期动态的创建该对象类的一个子类,类名就是在该类的前面加上NSKVONotifying_的前缀,子类并重写了任何被监听属性的setter方法,并使用willChangeValueForKey和didChangeValueForKey即手动触发方式来实现,这么做是基于设置属性会调用setter方法(KVC协议)。
并且系统将这个被监听的对象(B)的isa指针指向新生成的子类,那么这个对象其实就成为该子类的对象了。
解释为什么被监听对象(B)怎么就变成了一个子类对象,看NSObject结构
  1. @interface NSObject <NSObject> {
  2. Class isa  OBJC_ISA_AVAILABILITY;</font></font></font>
  3. }
复制代码

发现NSObject对象其实就是维护一个isa指针,可以这么理解,NSObject其实就是一个驱壳,而真正的控制这个类的是isa指针。

好,我们直接上代码看addObserver方法前后对象person变化的结果,代码如下:
  1. NSLog(@"before:%@", [personclass]);
  2. NSLog(@"before:%@",object_getClass(person));
  3. [personaddObserver:selfforKeyPath:@"name"options:NSKeyValueObservingOptionNewcontext:nil];
  4. NSLog(@"after:%@", [personclass]);
  5. NSLog(@"after:%@",object_getClass(person));
复制代码

日志log结果:

注意:如果你的属性监听是手动开启,即automaticallyNotifiesObserversForKey方法返回是NO,看到的日志结果都是Person类。

我们已经看到了前后的变化,那么继续看子类变化,子类其实是在操作了addObserver生成的,代码如下:
  1. NSLog(@"before:%@", [PersonfindAllOf:[Personclass]]);
  2. [personaddObserver:selfforKeyPath:@"name"options:NSKeyValueObservingOptionNewcontext:nil];
  3. NSLog(@"after:%@", [PersonfindAllOf:[Personclass]]);
复制代码

日志log结果:

findAllOf:代码方法如下:
  1. + (NSArray *)findAllOf:(Class)defaultClass
  2. {
  3. int count =objc_getClassList(NULL,0);
  4. if (count <=0){
  5. return [NSArrayarrayWithObject:defaultClass];
  6. }
  7. NSMutableArray *output = [NSMutableArrayarrayWithObject:defaultClass];
  8. Class *classes = (Class *) malloc(sizeof(Class) * count);
  9. objc_getClassList(classes, count);
  10. for (int i =0; i < count; ++i) {
  11. if (defaultClass ==class_getSuperclass(classes)){//子类
  12. [output addObject:classes];
  13. }
  14. }
  15. free(classes);
  16. return [NSArrayarrayWithArray:output];
  17. }
复制代码


补充一下,重写willChangeValueForKey和didChangeValueForKey这两个方法,填上日志,可以看到KVO通知自动触发方式调用了这两个方法,代码如下:
  1. - (void)willChangeValueForKey:(NSString *)key
  2. {
  3.     NSLog(@"%s",__func__);
  4.     NSLog(@"%@", [selfclass]);
  5.     NSLog(@"%@",object_getClass(self));
  6.     [superwillChangeValueForKey:key];
  7. }
  8. - (void)didChangeValueForKey:(NSString *)key
  9. {
  10.     NSLog(@"%s",__func__);
  11.     [superdidChangeValueForKey:key];
  12. }
复制代码

日志Log结果:

从日志中也可以看出真正调用willChangeValueForKey:和didChangeValueForKey:的是NSKVONotifying_Person子类对象。

看到这里,结合上篇,那么KVO和KVC的关系就很明了了,当被监听的对象属性值发生变化,值的变化是通过KVC方法来实现的,而KVO重写了KVC的方法,从而到达了一个监听的目的。

回复

使用道具 举报

*滑动验证:
您需要登录后才可以回帖 登录 | 加入社区

本版积分规则

推荐阅读 More>
广告位

Powered by Discuz X3.2

© 2001-2016   

合作伙伴

返回顶部 返回列表