欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

iOS KVO的使用和原理实现

程序员文章站 2022-04-13 15:33:36
...
文章目录 
   一、KVC(键值编码 key-value-coding)
      1、概念
      2、简单使用
        1> 注册观察者,添加观察对象属性
        2> 实现回调方法
        3> 移除观察者
        4> 测试触发kvo
      3、注意细节
   二、KVO(键值监听 key-value-observer)
     1、概念
     2、如何使用
     3、使用注意细节
  三、KVO实现原理
     1、原理总纲
     2、验证原理
     3、自己手动实现

一、KVC(键值编码)

1、概念 : 指在iOS的开发中,可以允许开发者通过Key名直接访问对象的属性,或者给对象的属性赋值。而不需要调用明确的存取方法。这样就可以在运行时动态在访问和修改对象的属性。

2、简单实用
定义一个person类,并在.h(.m)文件声明 name (NSString)属性

      // 1、 创建对象
      self.jack = [[Person alloc]init];

      // 2、赋值 和取值
      [jack setValue:@"jack" forKey:@"name"];
      NSLog(@"%@",[jack valueForKey:@"name"]);

     // 3、或者以下面的方式赋值和取值
     [self.jack setValue:@"tom" forKeyPath:@"name"];
     NSLog(@"%@",[self.jack valueForKeyPath:@"_name"]);


     // 4、 如何访问的key不存在,发生异常。如果捕获这个异常呢? 
     //    重写 setValue: forUndefinedKey: 重写后程序能避免crash
- (void)setValue:(id)value forUndefinedKey:(NSString *)key
{
    NSLog(@" %@---%@",value,key);
}

3、注意细节

1> key的值必须正确,如果拼写错误,会出现异常;

2> valueForKey\ valueForKeyPath 方法根据key的值读取对象的属性,
setValue:forKey:\ forKeyPath: 是根据key的值来写对象的属性;

3> 推荐使用 valueForKeyPath \ setValue:forKeyPth;

4> 当key的值是没有定义的,valueForUndefinedKey:这个方法会被调用,如果重写了这个方法,就可以获取错误的key值。

5> [jack setValuesForKeysWithDictionary: ] 传入一个字典,如果key捕获不存在的时候,可调用forUndefinedKey,可用于模型公用

6> [self.jack setValue:@"tom" forKeyPath:@"dog.name"];

表示:Person类面有成员变量dog,这个dog是Dog类是实例,具有name属性。

二、KVO(键值监听)

1、概念 : 当指定的对象的属性被修改后,则对象就会接受到通知。简单的说就是每次指定的被观察的对象的属性被修改后,KVO就会自动通知相应的观察者了。

2、如何使用

1> 注册观察者,并添加观察对象属性

// contex能确保是监听是当前类的属性(有可能父类和子类监听同一属性的情况)
static NSString const *PrivateKVOContext;

[self.jack addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:&
     PrivateKVOContext];

2> 实现回调方法

- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context
{
    NSLog(@"2 ->%@", object_getClass(self.jack));


    if ([keyPath isEqualToString:@"name"] && object == self.jack && context == &PrivateKVOContext) {

        id oldName = [change objectForKey:NSKeyValueChangeOldKey];

        NSLog(@"oldName----------%@",oldName);

        id newName = [change objectForKey:NSKeyValueChangeNewKey];
        NSLog(@"newName-----------%@",newName);


    }else{
        /*
         若当前类无法捕捉到这个KVO,那很有可能是在他的superClass,
         或者super-superClass...中
         */
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
    }

}     

3> 移除观察者

[self.jack removeObserver:self forKeyPath:@"name" context:&PrivateKVOContext];

4> 测试触发kvo


- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{

    // 1set 方法触发 kvo
    //jack.name = @"你好";

    // 2、KVC 触发kvo
   // [jack setValue:@"jack" forKey:@"name"];
      [self.jack setValue:@"jack" forKeyPath:@"name"];

}

3、细节注意

1> key 的值必须正确,如果拼写出现错误,会出现异常

2> 父类和子类同时存在KVO时(监听同一个对象的同一个属性),很容易出现对同一个keyPath进行两次removeObserver操作,从而导致程序crash。 要避免这个问题,就需要区分出KVO是self注册的,还是superClass注册的,我们可以在 -addObserver:forKeyPath:options:context:和-removeObserver:forKeyPath:context。这两个方法中传入不同的context进行区分。

3> 如果当前类无法捕捉kvo,有可能是父类中捕捉,所以,添加上[super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; 准没错

4> 触发kvo,可以是kvc,也可以是set方法

KVC和KVO简单使用

三、KVO原理实现

1、原理总纲

当某个类的实例对象的key第一次被观察时,系统就会在运行期动态地
创建该类的一个派生类NSKVONotifying_类名,
在这个派生类中重写该类中被观察的属性的 setter 方法。

2、验证原理

验证上述原理之前,我们需要了解下关于runtime的两个API

2.1 获取对象所属的类
iOS KVO的使用和原理实现

2.2 设置对象所属类(修改对象的isa)
iOS KVO的使用和原理实现

开始验证,拿 demo 验证下即可
iOS KVO的使用和原理实现

当添加属性观察者的时候,系统动态创建Person的派生类NSKVONotifying_Person, 并修改jack的isa,使其指向这个派生类(也就是说,jack就变成是NSKVONotifying_Person这个派生类的实例)。这样修改后,当 touch屏幕 修改属性的时候,调用的就是派生类重写的该属性的setter方法。

注意 : 由于这种继承方式的注入是在运行时而不是编译时实现的,如果给定的实例没有观察者,那么KVO不会有任何开销,因为此时根本就没有KVO代码存在。

3、手动实现原理

核心步骤如下 :
1> 生成一个Person类的派生类 Person_KVO。
2> 使当前对象的isa指向新的派生类,就会调用派生类的set方法。
3> 重写Person_KVO的setName方法,在set方法中拿到观察者(使用运行时关联观察者这个属性)。
4> 每次重写setName方法,都调用观察者的ml_observeValueForKeyPath方法。实现时刻监听。

手动实现KVO