KVO底层原理

来源:互联网 发布:ubuntu支持中文吗 编辑:程序博客网 时间:2024/05/29 15:28

了解更多iOS底层原理知识,关注腾讯课堂八点钟学院iOS高级开发 
iOS学习交流QQ群431449751

上一篇讲了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深度解析


0 0