美文网首页
KVO底层原理

KVO底层原理

作者: 前年的邂逅_Jerry | 来源:发表于2017-02-28 18:18 被阅读86次

    一、KVO基础操作

    添加观察者

        [_myClass addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
        //属性名
        NSLog(@"keyPath = %@",keyPath);
        //观察属性的对象,self
        NSLog(@"object = %@",object);
        //kind : 1 (新值) new : 属性的新值
        //NSKeyValueChangeSetting = 1,改变的操作
        //NSKeyValueChangeInsertion = 2,插入的操作 数组
        //NSKeyValueChangeRemoval = 3,移除的操作 数组
        //NSKeyValueChangeReplacement = 4,代替的操作 数组
        NSLog(@"change = %@",change);
        NSLog(@"context = %@",context);
    }
    

    在dealloc中移除观察者

        [_myClass removeObserver:self forKeyPath:@"name"];
    
    `NSKeyValueObservingOptionNew`:把更改之后的值提供给处理方法
    
    `NSKeyValueObservingOptionOld`:把更改之前的值提供给处理方法
    
    `NSKeyValueObservingOptionInitial`:把初始化的值提供给处理方法,一旦注册,立马就会调用一次。通常它会带有新值,而不会带有旧值。
    
    `NSKeyValueObservingOptionPrior`:分2次调用。在值改变之前和值改变之后。
    

    自动或者手动打开观察者

    + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key{
        //返回NO,手动来触发,否则自动触发。
        return YES;
    }
    

    如果设置成手动触发,当要改变属性值的时候需要加入如下代码:

    [_myClass willChangeValueForKey:@"name"];
    [_myClass setValue:@"KVC" forKey:@"name"];
    [_myClass didChangeValueForKey:@"name"];
    

    二、底层实现:运行时替换ISA指针。

    用黑魔法进行观察

        NSLog(@"myClassBefore:%@",[self.myClass class]);
        NSLog(@"runtimeBefore:%@",object_getClass(self.myClass));
        [_myClass addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
        NSLog(@"myClassAfter:%@",[self.myClass class]);
        NSLog(@"runtimeAfter:%@",object_getClass(self.myClass));
    

    打印结果:

    myClassBefore:MyClass
    runtimeBefore:MyClass
    myClassAfter:MyClass
    runtimeAfter:NSKVONotifying_MyClass
    

    对myClass对象的name属性添加观察,runtime会改变myClass对象的类,MyClass->NSKVONotifying_MyClass。如何证明NSKVONotifying_MyClassMyClass的子类?加入头文件#import <objc/runtime.h>,用runtime进行观察。

    - (void)viewDidLoad {
        [super viewDidLoad];   
        _myClass = [[MyClass alloc] init];
        NSLog(@"%@",[SecondController findSubClass:[_myClass class]]);
        [_myClass addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
        NSLog(@"%@",[SecondController findSubClass:[_myClass class]]);
    }
    + (NSArray *)findSubClass:(Class)defaultClass{
        //获取该类中所有类的个数
        int count = objc_getClassList(NULL, 0);
        if (count < 0) {
            return [NSArray array];
        }
        NSMutableArray * output = [NSMutableArray arrayWithObject: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 output;
    }
    

    打印结果如下:

    (
        MyClass
    )
    (
        MyClass,
        "NSKVONotifying_MyClass"
    )
    

    三、KVO对数组元素的监听

        [_myClass addObserver:self forKeyPath:@"arr" options:NSKeyValueObservingOptionNew context:nil];
    
        //KVO监听对数组中元素的变化
        [[_myClass mutableArrayValueForKey:@"arr"] addObject:@"add"];
        //这样子添加是监听不到的
        [_myClass.arr addObject:@"add"];
    

    为什么用下面的方法不能监听呢?KVO是基于KVC的,KVO能发送通知,都是通过KVC的方法处理的。
    NSLog(@"%@",[[_myClass mutableArrayValueForKey:@"arr"] class]);会自动生成NSMutableArray的子类:NSKeyValueNotifyingMutableArray,并重写了NSMutableArray相关的方法,如add,insert等(用黑魔法,runtime可以列出所有的方法),这里面添加了KVC的通信方法,当调用add这些方法的时候,就会触发通知。在add放里面添加了willChangeValueForKey和didChangeValueForKey方法来触发通知。

    相关文章

      网友评论

          本文标题:KVO底层原理

          本文链接:https://www.haomeiwen.com/subject/strxgttx.html