【MJ-iOS底层原理总结】探究KVO的本质

287 阅读2分钟
1. KVO的使用
  1. 假设当前有Person类,其中有一个int类型的属性age
    #import <Foundation/Foundation.h>
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface Person : NSObject
    
    @property (nonatomic, assign) int age;
    
    @end
    
    NS_ASSUME_NONNULL_END
    
  2. 在控制器中生成Person对象person,addObserver给person的age添加KVO监听,点击屏幕时改变person的age值
    #import "ViewController.h"
    #import "Person.h"
    
    @interface ViewController ()
    
    @property (nonatomic, strong) Person *person;
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        self.person = [Person new];
        self.person.age = 20;
        NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
        [self.person addObserver:self forKeyPath:@"age" options:options context:nil];
    }
    
    -(void)dealloc{
        [self.person removeObserver:self forKeyPath:@"age"];
    }
    
    -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
        self.person.age = 10;
    }
    
    -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
        NSLog(@"监听到%@的%@属性值改变了 - %@", object, keyPath, change);
    }
    
    @end
    
  3. 点击屏幕,输出结果:
    监听到<Person: 0x600001364530>的age属性值改变了 - {
        kind = 1;
        new = 10;
        old = 20;
    }
    
2. KVO的本质
  1. 在给person对象添加监听后,查看对象的isa,发现了这个对象的isa指向了名为NSKVONotifying_Person的类对象。由此推断,系统为Person类生成了名为NSKVONotifying_Person的子类,专门用于监听值变化。

    image.png image.png

  2. 内存结构分析

  • 未使用KVO监听的对象,内存结构如下:

    image.png

  • 使用了KVO监听的对象,内存结构如下:

    image.png

  1. NSKVONotifying_Person的猜想伪代码如下:

    #import "NSKVONotifying_MJPerson.h"
    
    @implementation NSKVONotifying_MJPerson
    
    - (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];
    }
    
    // 屏幕内部实现,隐藏了NSKVONotifying_MJPerson类的存在。
    - (Class)class{
        return [MJPerson class];
    }
    
    - (void)dealloc{
        // 收尾工作
    }
    
    - (BOOL)_isKVOA{
        return YES;
    }
    @end
    
  2. 为什么是_NSSetIntValueAndNotify?进行如下验证:

    self.person = [Person new];
    
    NSLog(@"person添加KVO监听之前 - %p", [self.person methodForSelector:@selector(setAge:)]);
    
    NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
    [self.person addObserver:self forKeyPath:@"age" options:options context:nil];
    
    NSLog(@"person添加KVO监听之后 - %p", [self.person methodForSelector:@selector(setAge:)]);
    

    打印结果如下: image.png

  3. 使用object_getClass(),可以获取到真实的类

    self.person = [Person new];
    
    NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
    [self.person addObserver:self forKeyPath:@"age" options:options context:nil];
    
    NSLog(@"%@", object_getClass(self.person));
    NSLog(@"%@", [self.person class]);
    

    输出结果如下: image.png

3. 总结,KVO的本质:
  • 利用RuntimeAPI动态生成了一个子类,并且让instance对象的isa指向这个全新的子类
  • 当修改instance对象的属性时,会调用Foundation的_NSSetXXXValueAndNotify函数
    • willChangeValueForKey:
    • 父类原来的setter
    • didChangeValueForKey:
  • 内部会触发监听器(Oberser)的监听方法(observeValueForKeyPath:ofObject:change:context:)