美文网首页
iOS_KVO本质解析

iOS_KVO本质解析

作者: Lin__Chuan | 来源:发表于2018-06-08 21:48 被阅读20次

    iOS 用什么方式实现对一个对象的KVO?(KVO的本质是什么)

    • 利用Runtime API动态生成一个子类, 并且让instance对象的isa指向这个全新的子类.
    • 当修改instance对象的属性时, 会调用Foundation 的 _NSSetXXValueAndNotify函数,这里面包括
      willChangeValueForKey:
      父类原来的Setter
      didChangeValueForKey:
      在didChangeValueForKey:中 会触发监听器(Observer)的监听方法observeValueForKeyPath:OfObject:change:context:)

    如何手动触发KVO?

    • 手动调用willChangeValueForKey:, didChangeValueForKey:两者
    • 若只调用didChangeValueForKey:, 则无法触发, 需两者一起调用

    KVO的基本使用

    1. 创建一个类World
    @interface World : NSObject
    @property(nonatomic,assign) NSInteger number;
    @end
    
    2. 在控制器中
    self.world = [World new];
    // 给对象添加KVO监听, 其 isa 会变成 NSKVONotifying_World, 而不是 World
    [self.world addObserver:self
                forKeyPath:@"number"
                   options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew
                   context:@"123"];
    // 默认为0
    self.world.number = 1;
    
    // 当监听 对象的属性 的值改变时调用
    -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
    {
        if ([keyPath isEqualToString:@"number"]) {
            NSLog(@"监听到%@的%@属性值发生改变%@ - %@",object, keyPath, change, context);
        }
    }
    

    KVO解析

    -(void)testKVO
    {
        self.world = [World new];
        self.world2 = [World new];
    
        // KVO之前
        NSLog(@"%p - %p",[self.world methodForSelector:@selector(setNumber:)],
                         [self.world2 methodForSelector:@selector(setNumber:)]);
    
        // 给对象添加KVO监听, 其 isa 会变成 NSKVONotifying_World, 而不是 World
        [self.world addObserver:self
                forKeyPath:@"number"
                   options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew
                   context:@"123"];
    
        // KVO之前
        NSLog(@"%p - %p",[self.world methodForSelector:@selector(setNumber:)],
                         [self.world2 methodForSelector:@selector(setNumber:)]);
    }
    

    打印两个对象的isa 做对比


    打印对象的isa .png

    这里用MJPerson类 做类比


    未使用KVO监听的对象.png 使用KVO监听的对象.png
    
    -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
    {
        // self.world对象的isa 为 NSKVONotifying_World, 不是World
        // NSKVONotifying_World 是系统利用Runtime机制动态创建的一个类
        [self.world setNumber:11];   
        // 调用的是 Foundation`_NSSetLongLongValueAndNotify)
       // 在这里面会调用bserveValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
        
        [self.world2 setNumber:22];  
       // 调用的是 TestKVO`-[World setNumber:] at World.m:13)
    }
    

    对比KVO监听对象的属性的set方法调用的差异.png

    相关文章

      网友评论

          本文标题:iOS_KVO本质解析

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