KVO 的相关问题

2023-04-12  本文已影响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;

    }

上一篇 下一篇

猜你喜欢

热点阅读