美文网首页
KVO的底层分析

KVO的底层分析

作者: CoderGuogt | 来源:发表于2019-08-12 12:39 被阅读0次

    KVO底层分析

    KVO的全称是 Key-Value Observing,俗称“键值监听”,可以用于监听某个对象属性值的改变

    基本使用

    添加一个成员变量

    @property (nonatomic, strong) YXCPerson *person;
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        _person = [YXCPerson new];
        _person.name = @"Jack";
        
        NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
        [_person addObserver:self forKeyPath:@"name" options:options context:nil];
    }
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        
        self.person.name = @"Tom";
    }
    
    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
        
        NSLog(@"%@的%@属性发生了改变:%@", object, keyPath, change);
    }
    

    点击模拟器,输出结果

    2019-08-12 11:22:27.251736+0800 KVO[18649:153721] <YXCPerson: 0x600002a1c410>的name属性发生了改变:{
        kind = 1;
        new = Tom;
        old = Tom;
    }
    
    

    以上就是KVO的基本使用

    为什么添加一个observe就能监听到属性的改变?再新建一个Person实例对象,不添加observe,通过同样的方式修改name属性的值,为什么添加了监听的实例对象就能监听?

    接下来,再新建一个Person实例对象,代码如下

    - (void)viewDidLoad {
        [super viewDidLoad];
        
        _person1 = [YXCPerson new];
        _person1.name = @"Jack";
        
        self.person2 = [YXCPerson new];
        self.person2.name = @"Tony";
        
        NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
        [_person1 addObserver:self forKeyPath:@"name" options:options context:nil];
    }
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        
        self.person1.name = @"Tom";
        self.person2.name = @"Kebi";
    }
    
    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
        
        NSLog(@"%@的%@属性发生了改变:%@", object, keyPath, change);
    }
    

    在添加监听之前,之后输出Person1Person2的类型

    2019-08-12 11:32:55.876017+0800 KVO[19438:193756] 添加监听之前:self.person1 : YXCPerson, self.person2 : YXCPerson
    2019-08-12 11:32:55.876745+0800 KVO[19438:193756] 添加监听之后:self.person1 : NSKVONotifying_YXCPerson, self.person2 : YXCPerson
    

    再看看NSKVONotifying_YXCPerson的父类是哪个类对象

    NSLog(@"person1的父类是:%@", [object_getClass(self.person1) superclass]);
    

    输出结果:

    2019-08-12 11:37:31.339343+0800 KVO[19513:198711] person1的父类是:YXCPerson

    在这里发现,添加了监听之后,Person1的类型变成了NSKVONotifying_YXCPerson,这是利用Runtime API 动态生成了额一个子类,并且让添加了监听的实例对象 isa指向了这个全新的子类.

    通过运行时获取 NSKVONotifying_YXCPerson 这个类的方法列表

    - (void)printfMethodNameWithClass:(Class)class {
        
        unsigned int count;
        // 获得方法数组
        Method *methodList = class_copyMethodList(class, &count);
        
        // 存储方法名
        NSMutableString *methodNames = [NSMutableString string];
        
        // 遍历所有的方法
        for (int i = 0; i < count; i++) {
            // 获得方法
            Method method = methodList[i];
            // 获得方法名
            NSString *methodName = NSStringFromSelector(method_getName(method));
            // 拼接方法名
            [methodNames appendString:methodName];
            [methodNames appendString:@", "];
        }
        
        // 释放
        free(methodList);
        
        // 打印方法名
        NSLog(@"%@ %@", class, methodNames);
    }
    

    输出结果

    NSKVONotifying_YXCPerson setName:, class, dealloc, _isKVOA,

    通过输出结果,可以发现这个全新的子类有四个方法 setName: class dealloc _isKVOA

    在通过全新的子类的setName:方法的地址,获取到实际上setName:的方法

    NSLog(@"%p", [self.person1 methodForSelector:@selector(setName:)]);
    

    0x10a3beb5e
    (lldb) p (IMP)0x10a3beb5e
    (IMP) $0 = 0x000000010a3beb5e (Foundation`_NSSetObjectValueAndNotify)

    这是发现 NSKVONotifying_YXCPerson 这个全新的类,在调用 setName:这个方法的时候,使用的是 Fundation 框架的一个 _NSSetObjectValueAndNotify C 语言方法

    _NSSetObjectValueAndNotify 的内部实现

    调用 willChangeValueForKey:
    调用父类的 setter 实现方法
    调用 didChangeValueForKey: (内部会调用observer的observeValueForKeyPath:ofObject:change:context方法)
    

    也可以通过

    [self.person1 willChangeValueForKey:@"name"];
    [self.person1 didChangeValueForKey:@"name"];
    

    手动触发监听

    相关文章

      网友评论

          本文标题:KVO的底层分析

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