美文网首页OC底层原理
OC底层原理(三):KVO

OC底层原理(三):KVO

作者: 跳跳跳跳跳跳跳 | 来源:发表于2020-12-25 20:54 被阅读0次

    KVO,全称为Key-Value Observing,可以用于监听某个类的属性值的改变

    KVO的使用

    我们创建一个iOS项目,然后新建一个ZJPerson类

    @interface ZJPerson : NSObject
    @property (nonatomic, assign) int age;
    @end
    

    然后在ViewController这个类中申明ZJPerson属性,在viewDidLoad中给ZJPerson添加KVO

    @interface ViewController ()
    @property (nonatomic, strong) ZJPerson *person;
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        _person = [[ZJPerson alloc]init];
        _person.age = 10;
        
        NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
        [_person addObserver:self forKeyPath:@"age" options:options context:nil];
    }
    
    - (void)dealloc {
        [_person removeObserver:self forKeyPath:@"age"];
    }
    
    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
    {
        NSLog(@"%@", change);
    }
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        _person.age = 20;
    }
    
    
    @end
    

    运行代码后,点击模拟器屏幕,输出如下


    截屏2020-12-21 21.44.46.png

    KVO的底层实现原理

    我们先修改下原来的代码,再初始化一个ZJPerson对象

    @implementation ViewController
    - (void)viewDidLoad {
        [super viewDidLoad];
        _person1 = [[ZJPerson alloc]init];
        _person1.age = 11;
        
        NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
        [_person1 addObserver:self forKeyPath:@"age" options:options context:nil];
        
        _person2 = [[ZJPerson alloc]init];
        _person2.age = 12;
    }
    
    - (void)dealloc {
        [_person1 removeObserver:self forKeyPath:@"age"];
        [_person2 removeObserver:self forKeyPath:@"age"];
    }
    
    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
    {
        NSLog(@"%@", change);
    }
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        _person1.age = 21;
        _person2.age = 22;
    }
    @end
    

    运行起来之后,点击屏幕输出如下


    截屏2020-12-22 21.19.56.png

    可以看到只有person1这个对象触发了KVO的回调,为啥会这样呢?
    我们打印两个person对象的isa看下区别

    isa.png

    可以看到添加了监听的person1对象的isa指向的是NSKVONotifying_ZJPerson这个类对象,而没有添加的监听的person2对象的isa指向的是ZJPerson类对象
    那么NSKVONotifying_ZJPerson这个又是什么东西呢?

    NSKVONotifying_ZJPerson是runtime动态创建出来的类,是ZJPerson的子类
    person2.png
    person1.png

    NSKVONotifying_ZJPerson内部的结构伪代码如下

    @implementation NSKVONotifying_ZJPerson
    
    - (void)setAge:(int)age {
        _NSSetIntValueAndNotify();
        
    }
    
    void _NSSetIntValueAndNotify() {
        [self willChangeValueForKey:@"age"];
        [super setAge:age];
        [self didChangeValueForKey:@"age"];
    }
    
    - (void)didChangeValueForKey:(NSString *)key {
        [self observeValueForKeyPath:@"age" ofObject:self change:nil context:nil];
    }
    
    @end
    

    我们通过代码来验证下
    在viewDidLoad中更新如下代码

    - (void)viewDidLoad {
        [super viewDidLoad];
        _person1 = [[ZJPerson alloc]init];
        _person1.age = 11;
        _person2 = [[ZJPerson alloc]init];
        _person2.age = 12;
        
        NSLog(@"before ---- person1 class is: %@", object_getClass(_person1));
        NSLog(@"before ----person2 class is: %@", object_getClass(_person2));
        
        NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
        [_person1 addObserver:self forKeyPath:@"age" options:options context:nil];
        
        NSLog(@"after ----person1 class is: %@", object_getClass(_person1));
        NSLog(@"after ----person2 class is: %@", object_getClass(_person2));
    }
    

    输出如下


    截屏2020-12-24 20.17.35.png

    可以看到没有添加的_person2指向的是ZJPerson类对象,添加了KVO的_person1对象,isa确实指向了NSKVONotifying_ZJPerson类对象。
    我们继续更新代码

    - (void)viewDidLoad {
        [super viewDidLoad];
        _person1 = [[ZJPerson alloc]init];
        _person1.age = 11;
        _person2 = [[ZJPerson alloc]init];
        _person2.age = 12;
        
    //    NSLog(@"before ---- person1 class is: %@", object_getClass(_person1));
    //    NSLog(@"before ----person2 class is: %@", object_getClass(_person2));
        NSLog(@"before ---- person1 method is: %p", [_person1 methodForSelector:@selector(setAge:)]);
        NSLog(@"before ---- person2 method is: %p", [_person2 methodForSelector:@selector(setAge:)]);
        
        NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
        [_person1 addObserver:self forKeyPath:@"age" options:options context:nil];
        
        NSLog(@"after ---- person1 method is: %p", [_person1 methodForSelector:@selector(setAge:)]);
        NSLog(@"after ---- person2 method is: %p", [_person2 methodForSelector:@selector(setAge:)]);
        
    //    NSLog(@"after ----person1 class is: %@", object_getClass(_person1));
    //    NSLog(@"after ----person2 class is: %@", object_getClass(_person2));
    }
    

    运行后输出如下


    截屏2020-12-24 20.34.41.png

    可以看到,在添加KVO之前,person1和person2的方法是相同的,再添加KVO之后,person1的方法就变了,我们打个断点调试,看看这个方法是什么,点击屏幕触发断点,在lldb中调试


    截屏2020-12-24 20.37.58.png
    打印出的方法也确实上面所提到的方法

    面试题

    1. KVO的本质是什么?
    • 利用rumtime API来动态生成一个全新的子类,并让添加KVO的instance对象的isa指向这个字类
    • 当instance属性改变时,调用_NSSetXXXValueAndNotify(XXX代表属性的类,比如属性为Int类型,方法名就是_NSSetIntValueAndNotify), 此方法内部实现如下
      • willChangeValueForKey:
      • 父类的set方法
      • didChangeValueForKey:
    1. 如何手动触发KVO?
      调用如下方法

      • willChangeValueForKey:
      • didChangeValueForKey:
    2. 直接修改成员变量会触发KVO吗?
      不会

    相关文章

      网友评论

        本文标题:OC底层原理(三):KVO

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