KVO 本质与验证过程

2023-04-13  本文已影响0人  笔头还没烂
  1. 第一部分:KVO 本质

    (1)使用 runtime 动态创建 NSKVONotifying_xxx 类,该类是之前原始类的子类,并且该类有自己 setter 方法的实现

    (2)原来的实例对象的 isa 指针指向新创建的类的类对象,这意味着:当实例对象的属性值发生改变时,实例对象的 isa 所指向的新的类对象中的 setter 方法会被调用

    (3)新的类对象的 setAge 方法本质上是调用了 NSSetXXXValueAndNotifying 函数

    (4)NSSetXXXValueAndNotifying 函数中,会先调用 willChangeValueForKey 方法,然后调用父类的 setter 方法将属性值改掉,再调用 didChangeValueForKey 方法

    (5)didChangeValueForKey 方法中,会通知 observer 调用 observerValueForKeyPath 的监听方法

  2. 第二部分:验证过程

    • 验证实例对象添加了 KVO 监听后,其 isa 指针指向一个新创建的类对象,代码如下:

      #import "ViewController.h"
      #import <objc/runtime.h>
      #import "Person.h"
      
      @interface ViewController ()
      @property (nonatomic,strong) Person *person1;
      @end
      
      @implementation ViewController
      
      - (void)viewDidLoad {
          [super viewDidLoad];
          // Do any additional setup after loading the view.
          
          self.person1 = [[Person alloc] init];
          self.person1.age = 10;
          
          //验证第一小点
          Class cls1 = object_getClass(self.person1);
          NSLog(@"%@",NSStringFromClass(cls1));
          
          //给 person 对象添加 KVO监听
          [self.person1 addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
          
          //验证第一小点
          Class cls2 = object_getClass(self.person1);
          NSLog(@"%@",NSStringFromClass(cls2));
          
      }
      

      输出结果如下:

      Person

      NSKVONotifying_Person

      由上面的打印结果可以看出,实例对象添加了 KVO 监听后 isa 指针指向的类对象确实发生了改变。

    • 验证实例对象添加了 KVO 监听后其 isa 指针指向的新的类对象有自己的 setter 方法实现,代码如下:

      #import "ViewController.h"
      #import <objc/runtime.h>
      #import "Person.h"
      
      @interface ViewController ()
      @property (nonatomic,strong) Person *person1;
      @end
      
      @implementation ViewController
      
      - (void)viewDidLoad {
          [super viewDidLoad];
          // Do any additional setup after loading the view.
          
          self.person1 = [[Person alloc] init];
          self.person1.age = 10;
          
          IMP impl1 = [self.person1 methodForSelector:NSSelectorFromString(@"setAge:")];
          NSLog(@"%p",impl1);
          
          //给 person 对象添加 KVO监听
          [self.person1 addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
          
          IMP impl2 = [self.person1 methodForSelector:NSSelectorFromString(@"setAge:")];
          NSLog(@"%p",impl2);
      }
      
      - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
          self.person1.age = 20;
      }
      
      //当监听对象对象的属性值发生改变时,就会调用
      - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
          NSLog(@"监听到 %@ 的 %@ 属性值改变了 -- %@",object,keyPath,change);
      }
      
      - (void)dealloc {
          [self.person1 removeObserver:self forKeyPath:@"age"];
      }
      @end
      

      输出结果如下:

      0x100f41388

      0x180b6167c

    • 在运行程序之前,我们可以在 viewDidLoad 方法的最后一行打下断点,然后运行程序,当控制台输出两个方法实现的地址值时,我们结合 lldb 动态调试器进行调试,如下所示:

      通过 lldb 动态调试器将上面的地址强制转成 IMP 类型输出,调试过程如下所示:

      0x100f41388

      0x180b6167c

      (lldb) p (IMP)0x100f41388

      (IMP) $0 = 0x0000000100f41388 (KVO`-[Person setAge:] at Person.h:13)

      (lldb) p (IMP)0x180b6167c

      (IMP) $1 = 0x0000000180b6167c (Foundation`_NSSetIntValueAndNotify)

      可以看到,添加 KVO 监听之前,属性的 setter 方法的地址对应的实现是 Person 类对象中的 setAge: 方法,并且该方法是通过“减号-中括号[ ]”的形式调用,很明显是 OC 的方法;而添加 KVO 监听之后, setter 方法的地址对应的实现就变成了Foundation 模块下的 _NSSetIntValueAndNotify,该方法很明显是通过 C 语言的形式来调用。至此第一部分 KVO 本质的前面 3 小点都得到了验证。

    • 后面三点的验证,代码如下:

      (1)Person 类的代码,我们在原始类(即 runtime 动态创建的子类 NSKVONotifying_xxx 的父类)中重写 willChangeValueForKey: 和 didChangeValueForKey: 两个方法,同时加入一些打印信息帮助我们分析,代码如下所示:

      • .h 文件

        #import <Foundation/Foundation.h>
        @interface Person : NSObject
        @property (nonatomic,assign) int age;
        @end
        
      • .m 文件

        #import "Person.h"
        @implementation Person
        - (void)willChangeValueForKey:(NSString *)key {
            [super willChangeValueForKey:key];
            NSLog(@"willChangeValueForKey");
        }
        - (void)didChangeValueForKey:(NSString *)key {
            NSLog(@"didChangeValueForKey - begin");
            [super didChangeValueForKey:key];
            NSLog(@"didChangeValueForKey - end");
        }
        @end
        

      (2)ViewController 的代码:

      • .m 文件

        #import "ViewController.h"
        #import <objc/runtime.h>
        #import "Person.h"
        
        @interface ViewController ()
        @property (nonatomic,strong) Person *person1;
        @end
        
        @implementation ViewController
        
        - (void)viewDidLoad {
            [super viewDidLoad];
            // Do any additional setup after loading the view.
            
            self.person1 = [[Person alloc] init];
            self.person1.age = 10;
            
            //给 person 对象添加 KVO监听
            [self.person1 addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
            
        }
        
        - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
            self.person1.age = 20;
        }
        
        //当监听对象对象的属性值发生改变时,就会调用
        - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
            NSLog(@"监听到 %@ 的 %@ 属性值改变了 -- %@",object,keyPath,change);
        }
        
        - (void)dealloc {
            [self.person1 removeObserver:self forKeyPath:@"age"];
        }
        @end
        

        运行后,输出结果如下:

        willChangeValueForKey

        didChangeValueForKey - begin

        监听到 <Person: 0x600002d34360> 的 age 属性值改变了 -- {

        kind = 1;

        new = 20;

        old = 10;

        }

        didChangeValueForKey - end

        至此, KVO 的本质已验证完成。

      以上,感谢阅读!

上一篇 下一篇

猜你喜欢

热点阅读