美文网首页
KVO 本质与验证过程

KVO 本质与验证过程

作者: 笔头还没烂 | 来源:发表于2023-04-13 23:40 被阅读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 的本质已验证完成。

      以上,感谢阅读!

相关文章

  • KVO和KVC的使用及原理解析

    一 KVO基本使用 二 KVO本质原理讲解及代码验证 三 KVC基本使用 四 KVC设值原理 五 KVC取值原理 ...

  • Objective-C的本质(4)—— KVO本质

    参考:iOS底层原理总结 - 探寻KVO本质iOS-KVO本质 问题一:kvo如果找到对应的属性 KVO不存在查找...

  • iOS原理篇(一): KVO实现原理

    KVO实现原理 什么是 KVO KVO 基本使用 KVO 的本质 总结 一 、 什么是KVO KVO(Key-Va...

  • iOS 基础知识回顾——关于 property

    目录: 本质 修饰符 synthesize, dynamic KVO 与 Property @property 本...

  • 深入理解KVO

    iOS | KVO | Objective-C KVO的本质是什么,如何手动触发KVO? 1.什么是KVO KVO...

  • KVC和KVO

    kvc和kvo的本质与底层调用 KVO 项目中使用的KVO监听键盘的弹起收回给webView添加加载进图条 添加了...

  • 04. KVO使用,原理,本质

    问题 KVO日常使用 KVO原理(KVO本质是什么) 如何手动触发KVO 直接修改成员变量会触发KVO吗 KVO图...

  • KVO 底层本质

    一、KVO 的一个疑惑 二、KVO 的浅层分析 三、KVO 浅层分析验证 四、KVO 子类内部方法 五、手动触发 ...

  • KVO 键值监听 / KVC 键值编码

    KVO(Key-Value Observing)本质是监听set方法,重写set方法。 过程: 1、动态生成一个 ...

  • KVO

    0:KVO的使用步骤 1:KVO本质 methodForSelector方法 NSKVONotifying_Per...

网友评论

      本文标题:KVO 本质与验证过程

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