KVO分析

2020-10-28  本文已影响0人  Rachel_雷蕾

上节研究完KVC后,随之关联的还有一个KVO,本篇就让我们来分析一下KVO的使用以及原理

一、KVO使用

对于添加观察对象方法:addObserver,可以根据官方文档KVO官方查询相关用法,其中

NSKeyValueObservingOptionNew:观察属性的新值
NSKeyValueObservingOptionOld:选择从更改之前接收观察到的属性的值
NSKeyValueObservingOptionInitial:可以使用此附加的一次性通知在观察者中建立属性的初始值
NSKeyValueObservingOptionPrior:可以指示观察到的对象在属性更改之前发送通知(除了更改之后的常规通知)。变更字典表示变更前通知,方法是将键NSKeyValueChangeNotificationIsPriorKey与包装为YES的NSNumber的值包含在一起

注:
1、如果没有注册观察员,则请求将其删除为观察员会导致NSRangeException
2、释放时,观察者不会自动删除自身。被观察对象继续发送通知,而不考虑观察者的状态。但是,与任何其他消息一样,发送到已发布对象的更改通知会触发内存访问异常。因此,你要确保观察者在从内存中消失之前将自己移除。
3、没有提供询问对象是观察者还是被观察者的方法。构造代码以避免相关错误。一种典型的模式是在观察者初始化期间(例如在init或viewdiload中)注册为观察者,并在释放期间注销(通常在dealoc中),确保正确配对和有序地添加和删除消息,并且在将观察者从内存中释放之前取消注册。

-是否可以 不移除:不可以。否则会崩溃,观察对象没被移除,但是观察者已经被释放了,再次注册时,添加观察器,消息发送后,系统不知道应该由哪个观察器接受。造成指针混乱(
由于第一次注册KVO观察者后没有移除,再次进入界面,会导致第二次注册KVO观察者,导致KVO观察的重复注册,而且第一次的通知对象还在内存中,没有进行释放,此时接收到属性值变化的通知,会出现找不到原有的通知对象,只能找到现有的通知对象,即第二次KVO注册的观察者,所以导致了类似野指针的崩溃,即一直保持着一个野通知,且一直在监听)

image.png

找到文档里关于观察数组时的要求:观察数组要按照kcv的形式赋值,才能发送更改消息
那么将数组按照kvc形式赋值,更改


image.png

结果收到了更改的消息,且类型kind为2,是NSKeyValueChange的值,查到NSKeyValueChange定义,有如下四种值的改变方式

image.png

二、KVO底层
都知道,KVO只能观察属性,不能观察成员变量,这个也有在代码里验证过,只能是属性可以被观察,这是为什么呢,属性和成员变量的区别就在于,多了setget方法。说明,是kvo
只能观察set方法,捕捉到了值的变化,下面让我们来验证

#pragma mark - 遍历方法-ivar-property
- (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);
}

顺便添加一下打印类名的方法

#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);
}

调用


image.png

发现派生类实际上是当前类的子类,且重新生成set方法

注:为什么方法继承:因为继承的话,不会在子类中显示,只在父类中,这一点也可以添加LGPerson类的一个子类再打印一次方法列表试试,对比结果,也可以印证这一点。

确实调用完removeObserver后,isa再指回原类了。

image.png
派生类根本就不移除了:因为KVO派生类只要生成,就会一直存在,这样可以减少频繁的添加操作

至此,整个KVO原理大致流程明白了:创建派生类实现了键值观察。

添加:addObserver时,创建了派生类,派生类是当前类的子类重写了被监听属性的setter方法,并将当前类的isa指向了派生类。(此时开始,所有调用本类的方法,都是调用的派生类。派生类中没有的方法,就会沿着继承链查询到本类)

改变属性值: 派生类重写了被监听属性的setter方法,在派生类的setter方法触发时:在willChange之后didChange之前,调用父类属性setter方法,完成父类属性的赋值。

移除: 在removeObserver后,isa派生类指回本类。 但创建过的派生类会被本类从子类列表中移除,会一直存在。

假象: 外部打印class永远看不到派生类,是因为派生类将class方法重写了,故意不让外界看到。

上一篇下一篇

猜你喜欢

热点阅读