KVO原理总结

iOS 用什么方式实现对一个对象的KVO?

  1. 利用runtime api动态生成一个子类NSKVONotifying_xxx, 子类的superclass指向原来类,并让对象的isa指针指向这个新生成的子类。
  2. 新生成的子类会重写setter方法,当外部调用setter方法修改属性时,通过isa指针找到子类,调用新的setter方法,其内部会调用foundation框架的_NSSetxxxValueAndNotify函数,里面的xxx是属性的类型。
  3. _NSSetxxxValueAndNotify函数内部有以下调用:willChangeValueForKey: -> 父类原来的setter方法 -> didChangeValueForKey:
  4. didChangeValueForKey: 方法的内部会使属性的监听者们调用每个监听者的方法- (void)observeValueForKeyPath:ofObject:change:context:
  5. 由上可知,如果是自己手动给成员变量赋值,不会调用到setter方法,也就不会触发KVO; 如果要手动触发KVO,需要自己手动调用willChangeValueForKey:didChangeValueForKey: 方法,只调用后者是无效的. 或者使用KVC赋值。

疑问点:

一、添加KVO观察者时的context是什么作用?

比如父类的实例对象和子类的实例对象都对一个对象的相同的keypath添加了监听,可通过context来区别,移除时也可以传context来移除对应的监听。所以总的来说context是用于区别对相同对象相同keypath监听。
KVO里面的(void *)context

二、有哪些方法可以拦截观察的情况
//1. 返回YES则是自动通知观察者,返回NO的话需要手动调用下面两个方法才会通知观察者。
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key;
//2. 可以在观察对象的类中重写下面两个方法,通过打印来了解KVO调用过程
//3. 重写时需要加上[super ...],否则就拦截了所有的KVO了,我们也可以在这里做筛选操作
- (void)willChangeValueForKey:(NSString *)key;
- (void)didChangeValueForKey:(NSString *)key;

那接下来的willChangeValueForKeydidChangeValueForKey方法里面又做了什么?
GNUstep源码下载地址:http://www.gnustep.org/resources/downloads.php

KVO原理总结_第1张图片
GUNStep.png

willChangeValueForKey中做的主要操作:
设置旧值到change中,遍历观察者根据需要看是否要通知观察者

[pathInfo->change setObject:old forKey:NSKeyValueChangeOldKey];

didChangeValueForKey中做的主要操作:
设置新值到change中,遍历观察者根据需要看是否要通知观察者。

  • 如果是不调用KVC或者setter方法的情况下,修改成员变量值时触发KVO,需手动触发:
[self willChangeValueForKey:@"name"];
_name=name;
[self didChangeValueForKey:@"name"];
  • 如果要实现达到条件才触发允许触发KVO?
+(BOOL)automaticallyNotifiesObserversForKey:(NSString *)key{
    if ([key isEqualToString:@"name"]) {// 取消系统kvo通知
        return NO;
    }else{
        return [super automaticallyNotifiesObserversForKey:key];
    }
}
-(void)setName:(NSString *)name{
    
    if (_name!=name) {
        // 手动触发KVO
        [self willChangeValueForKey:@"name"];
        _name=name;
        [self didChangeValueForKey:@"name"];
    }
      
}
三、系统怎么管理KVO观察者的?
  1. 用什么来保存观察者对象的?
    由一个全局的infotable来记录所有被观察的对象的KVO情况,key=对象地址,value=一个KVOInfo对象;KVOInfo对象中记录了被观察的keypaths数组,每一个keypath对应着观察者对象的信息数组KVOObservertions; KVOObservertion中有观察者对象observer、option、context。
四、 _NSSetxxxValueAndNotify函数内部的实现是怎么样?
  1. 怎么证明调用了这个函数的?


    调用setter方法时.png

调用完willChangeValueForKey和setter之后.png

willChangeValueForKey: -> 父类原来的setter方法 -> didChangeValueForKey:
didChangeValueForKey: 方法的内部会使属性的监听者们调用每个监听者的方法- (void)observeValueForKeyPath:ofObject:change:context:

五、新生成的这个子类增加了什么属性?增加了什么方法?

下面是运行的打印信息:

新生成的子类的类名是:NSKVONotifying_Student
新生成的子类的父类名是:Student
新生成的子类中添加的实例方法有:
方法0: 名字:setAge:
方法1: 名字:class
方法2: 名字:dealloc
方法3: 名字:_isKVOA
 ---------------------
新生成的子类中添加的类方法有:
---------------------
新生成的子类中添加的属性有:
六、新生成的这个子类在什么时候会自动移除?

在对象的所有观察者都被移除时,会移除子类并且本身对象isa重置为原来的,这点通过移除所有观察者后打印可以验证。

NSLog(@"新生成的子类的类名是:%@", NSStringFromClass(object_getClass(self)));
七、添加了KVO观察的对象,在所有观察者被移除之前销毁了会奔溃

在viewDidLoad中添加下面代码,在作用域{}之后student会销毁,此时奔溃

{
     Student *student = [[Student alloc] init];
     [student addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:@"second observer!"];
     [student printKVOClassInfo];
}

报错:
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'An instance 0x7b411190 of class Student was deallocated while key value observers were still registered with it. Current observation info: ( Context: 0x65190, Property: 0x7b425ec0> )'

KVO进阶 —— 源码实现探究

你可能感兴趣的:(KVO原理总结)