美文网首页
KVO 的相关问题

KVO 的相关问题

作者: 笔头还没烂 | 来源:发表于2023-04-12 16:05 被阅读0次
    1. iOS 中,实例对象添加了 KVO 监听之后,该实例对象调用 class 方法和执行 object_getClass 并传入该实例对象有什么区别?

      结论:调用实例对象的 class 方法会返回该对象所属的类,而执行 object_getClass 并传入该实例对象会返回该对象的 isa 指针所指向的类。在普通的情况下,这两者是相同的。但是如果该实例对象所属的类是一个KVO子类,那么调用 class 方法会返回该实例对象原始的类,而执行 object_getClass 并传入该实例对象会返回 KVO 子类。这是因为 KVO 会通过运行时机制动态生成一个子类来代理原始类,从而实现 KVO 监听。
      查看了苹果 runtime 的 NSObject 的 - (Class)class 官方源码,源码如下:

      - (Class)class {
          return object_getClass(self);
      }
      
      • 可见,NSObject 的 class 实例方法的底层实现就是走了 object_getClass 方法。
      • 通过代码测试,当实例对象添加了 KVO 监听之后,调用 class 会返回该实例对象原始的类。由此可见,添加了 KVO 监听的实例对象的 isa 指向一个通过 runtime 机制动态生成的子类类对象,该类对象的方法列表中重写了 class 方法,返回了原始类,从而屏蔽了内部实现,隐藏了 NSKVONotifying_XXX 类的存在。
    2. 怎么证明 Person 实例对象 p1 添加了 KVO 监听之后,程序动态创建一个 NSKVONotifying_Person 类是 Person 类的子类?

      • 我们知道类对象的 superclass 指针指向它的父类的类对象,因此可以通过 superclass 指针来获取。示例代码如下:
      #import "ViewController.h"
      #import <objc/runtime.h>
      
      struct gq_object_class {
          Class isa;
          Class superclass;
      };
      
      @interface Person : NSObject
      @property (nonatomic,assign) int age;
      @end
      
      @implementation Person
      
      @end
      
      @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;
          struct gq_object_class *p1Cls = (__bridge struct gq_object_class *)object_getClass(self.person1);
          NSLog(@"%p",p1Cls->superclass);
      }
      
      //当监听对象对象的属性值发生改变时,就会调用
      - (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
      

      程序运行,当点击手机屏幕时,控制台输出地址值,通过 po 打印该地址值所指向的对象,如下所示:

      0x100a61168

      (lldb) po 0x100a61168

      Person

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

      利用 RuntimeAPI 动态生成一个子类,并且让 instance 对象的 isa 指向这个全新的子类;

      当修改 instance 对象的属性时,会调用属性的 setter 方法,并在该 setter 方法中调用 Foundation 的 _NSSetXXXValueAndNotify 函数。_NSSetXXXValueAndNotify函数内部主要做下面三件事情:

      • 调用实例对象的 willChangeValueForKey: 方法;
      • 调用父类原来的 setter 方法;
      • 调用实例对象的 didChangeValueForKey: 方法
        • didChangeValueForKey: 方法内部又会触发监听器(Observer)的监听方法(ObserveValueForKeyPath:ofObject:change:context:)

      伪代码如下所示:
      (1)NSKVONotifying_Person.h

      #import "MJPerson.h"
      @interface NSKVONotifying_Person : MJPerson
      @end
      

      (2)NSKVONotifying_Person.m

      #import "NSKVONotifying_Person.h"
      @implementation NSKVONotifying_Person
      - (void)setAge:(int)age
      {
          _NSSetIntValueAndNotify();
      }
      // 伪代码
      void _NSSetIntValueAndNotify()
      {
          [self willChangeValueForKey:@"age"];
          [super setAge:age];
          [self didChangeValueForKey:@"age"];
      }
      - (void)didChangeValueForKey:(NSString *)key
      {
          // 通知监听器,某某属性值发生了改变
          [oberser observeValueForKeyPath:key ofObject:self change:nil context:nil];
      }
      @end
      
    2. 如何手动触发 KVO ?

      问题描述:KVO 的使用场景一般是当值被修改了,系统会自动帮我们调用 KVO 的监听方法(自动触发);现在想实现的是,在值没有被修改的情况下,KVO 的监听方法也能被我们调用(手动触发)。

      示例代码如下:

      #import "ViewController.h"
      #import <objc/runtime.h>
      #import "Person.h"
      
      @interface ViewController ()
      @property (nonatomic,strong) Person *person1;
      @property (nonatomic,strong) Person *person2;
      @end
      
      @implementation ViewController
      
      - (void)viewDidLoad {
          [super viewDidLoad];
          // Do any additional setup after loading the view.
          
          self.person1 = [[Person alloc] init];
          self.person1.age = 10;
          
          self.person2 = [[Person alloc] init];
          self.person2.age = 5;
          
          //给 person 对象添加 KVO监听
          [self.person1 addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
          
      }
      
      - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
          [self.person1 willChangeValueForKey:@"age"];
          [self.person1 didChangeValueForKey:@"age"];
      }
      
      //当监听对象对象的属性值发生改变时,就会调用
      - (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
      

      当程序运行后点击手机屏幕时,输出结果如下:

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

      kind = 1;

      new = 10;

      old = 10;

      }

    相关文章

      网友评论

          本文标题:KVO 的相关问题

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