• Runtime——KVC,KVO原理


    KVC是什么?

    KVCKey Value Coding的缩写,意思是键值编码。 在iOS中,提供了一种方法通过使用属性的名称(也就是Key)来间接访问对象属性的方法,这个方法可以不通过getter/setter方法来访问对象的属性。 用KVC可以间接访问对象属性的机制。通常我们使用valueForKey 来替代getter 方法,setValue:forKey来代替setter方法。

    常用API:

    - (void)setValue:(id)value forKeyPath:(NSString *)keyPath;
    - (void)setValue:(id)value forKey:(NSString *)key;
    - (id)valueForKeyPath:(NSString *)keyPath;
    - (id)valueForKey:(NSString *)key; 
    
    • 1
    • 2
    • 3
    • 4

    setValue:forKey:和valueForKey:只能用来访问当前对象的属性,而keyPath可以通过当前对象访问属性的属性,可以一层一层套下去。

    简单举个例子:

    #import <Foundation/Foundation.h>
    #import "Car.h"
    NS_ASSUME_NONNULL_BEGIN
    
    @interface Person : NSObject 
    @property (nonatomic, assign) NSInteger age;
    
    @property (nonatomic, strong) Car* car;
    
    @end
    
    NS_ASSUME_NONNULL_END```
    #import <Foundation/Foundation.h>
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface Car : NSObject 
    
    @property (nonatomic, strong) NSString* name;
    @end
    #import "ViewController.h"
    #import "Person.h"
    @interface ViewController ()
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        // Do any additional setup after loading the view.
        Person* person = [[Person alloc] init];
        person.car = [[Car alloc] init];
        // KVC
        [person setValue:@"20" forKey:@"age"];
        NSLog(@"%@", [person valueForKey:@"age"]);
        [person setValue:@111 forKeyPath:@"car.name"];
        NSLog(@"%@", [person valueForKeyPath:@"car.name"]);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    KVC的底层原理

    KVC赋值

    keypath的底层和key是同理的,下面分析一个key的流程,上一张网上很明白的图。
    在这里插入图片描述
    下面分析一下步骤:

    • 首先第一步,先查找该类的setter方法,首先会把参数key的首字母大写,如何根据Key去查找对应的setter方法,包括setKey_setKey,如果找到了就调用这个方法并且传入参数value,从而修改值,如果没有找到setter方法,就回去查看该类的+accessInstanceVariablesDirectly类方法的返回值(能不能直接访问成员变量,默认为YES),如果不能直接访问,就会报错,并且触发“setValue:forUndefinedKey:"方法。
    • 如果能访问成员变量,会按照顺序查找对应的成员变量,_key,_isKey,key,isKey,如果找到了对应的成员变量,就直接给成员变量赋值,(如果设置了KVO的话,此时会触发KVO,因为KVC在此处,也调用了willChangeValueForKey:didChangeValueForKey:方法),如果没找到成员变量,就会报错,并且触发“setValue:forUndefinedKey:"方法。

    KVC取值

    在这里插入图片描述

    • 类似于上面的流程,只不过改为先查找getter方法,按照getKey,key,isKey,_key顺序查找
    • 如果没有找到getter方法,就回去查看该类的+accessInstanceVariablesDirectly类方法的返回值(能不能直接访问成员变量,默认为YES),如果不能直接访问,就会报错,并且触发“valueForUndefinedKey:"方法。
    • 如果能访问成员变量,会按照顺序查找对应的成员变量,_key,_isKey,key,isKey,如果没找到,就会报错,并且触发“valueForUndefinedKey:"方法。

    KVO?

    KVO的全称是Key-Value Observing,俗称“键值监听”,可以用于监听某个对象属性值的改变。
    在这里插入图片描述
    使用KVO只有三个关键点:被观察是谁:即想要观察哪个对象哪个属性值变化;观察者是谁:谁观察这个对象,确定后就可以给对象添加KVO和移除对象的KVO了;观察者的回调方法:对象属性变化后触发的方法.
    举个例子:

    #import "ViewController.h"
    #import "Person.h"
    @interface ViewController ()
    @property (nonatomic, strong) Person* person;
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        // Do any additional setup after loading the view.
        self.person = [[Person alloc] init];
        self.person.car = [[Car alloc] init];
        // KVO
        // 给person添加观察者
        [self.person addObserver:self forKeyPath:@"age" options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) context:nil];
        // KVC
        [self.person setValue:@"20" forKey:@"age"];
        NSLog(@"%@", [self.person valueForKey:@"age"]);
        [self.person setValue:@111 forKeyPath:@"car.name"];
        NSLog(@"%@", [self.person valueForKeyPath:@"car.name"]);
    }
    
    - (void)dealloc {
        // 移除person对象的KVO
        [self.person removeObserver:self forKeyPath:@"age"];
    }
    // 回调方法
    -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
        NSLog(@"被观察对象%@的%@属性发生变化:%@", object, keyPath, change);
    }
    @end
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    具体使用以及参数含义可以参考我之前的博客——简单使用KVO

    在这里插入图片描述

    KVO底层原理

    既然是利用了Runtime实现的KVO,就来探索一下,到底是如何实现的。
    先下结论。

    • 首先在运行时在利用Runtime给这个类新建一个子类,并且将使用了KVO的instance的isa指针指向这个新建的类。此时这个实例对象已经不是原来的类了,而是属于新建的子类。
    • 接下来这个类会重写原来类以及NSObject类的部分方法,结合之前KVC找setter方法,我们可以想到最重要的就是重写实例方法setter。
    • 重写后的setter主要三个流程:首先调用willChangeValueForKey:表示即将修改值,然后找对象的setter方法:真正去修改成员变量的值,最后调用didChangeValueForKey:并且在这个方法内实现观察者调用观察者回调方法。
      可以看看它的大概伪代码:
    @interface NSKVONotifying_Person : Person
    @end
    
    @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];
    }
    
    // 屏幕内部实现,隐藏了NSKVONotifying_MJPerson类的存在
    - (Class)class
    {
        return [Person class];
    }
    
    - (void)dealloc
    {
        // 收尾工作
    }
    
    - (BOOL)_isKVOA
    {
        return YES;
    }
    
    @end
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    在这里插入图片描述

  • 相关阅读:
    《每日一题》NO.46:何为WAT/CP/FT?
    java基础---NIO
    【每日运维】U盘启动盘安装 ESXi 6.7.0 安装卡在 loading /bnxtroce.v00
    bp神经网络怎么看结果,bp神经网络结果不一样
    关于EEGLAB安装时报错“未定义函数或者变量‘EEGLAB’”
    H3C 6520X版本U盘升级
    国产软件Bigemap与国产在线地图源<星图地球数据云>推动国内新GIS应用
    【Java面试】Zookeeper中的Watch机制的原理?
    编写ESM风格的nodejs应用
    智云通CRM:如何使用万能的“三问”,提升业绩?
  • 原文地址:https://blog.csdn.net/chabuduoxs/article/details/126166600