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; 

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"]);
}

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

具体使用以及参数含义可以参考我之前的博客——简单使用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

在这里插入图片描述

扫描二维码关注公众号,回复: 14691138 查看本文章

猜你喜欢

转载自blog.csdn.net/chabuduoxs/article/details/126166600