KVC的本质是什么?

语言: CN / TW / HK

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。


KVC全称是Key-Value Coding,

俗称“键值编码”,可以通过一个key来访问某个属性。

常见的API有:

```

  • (void)setValue:(id)value forKeyPath:(NSString*)keyPath;

  • (void)setValue:(id)value forKey:(NSString*)key;

  • (id)valueForKeyPath:(NSString*)keyPath;

  • (id)valueForKey:(NSString*)key;

```

前两个是用来设置值的,后边两个是取值的。

先来一个小demo演示一下KVC基本用法:

创建一个Person类。

``` @interface Person : NSObject

@property (assign, nonatomic) int age;

@end

@implementation Person

@end

Person *person = [[Person alloc] init];

我们给person某个属性设置值,我们一般用set方法:

person.age = 10;

我们取person 的某个的值,我们一般用get方法:

person.age

实际上,我们也可以用:

[person setValue:@10 forKey:@"age"];

来给person对象的age属性赋值。

可以用:

NSLog(@"%@", [person valueForKey:@"age"]);

来获取person对象的age属性值。

或者用

[person setValue:@10 forKeyPath:@"cat.weight"];

来给person对象的age属性赋值。

NSLog(@"%@", [person valueForKeyPath:@"cat.weight"]);

来获取person对象的age属性值。

那么,

  • (void)setValue:(id)value forKeyPath:(NSString*)keyPath;

  • (void)setValue:(id)value forKey:(NSString*)key;

这两个函数有什么区别吗?

  • (id)valueForKeyPath:(NSString*)keyPath;

  • (id)valueForKey:(NSString*)key;

这两个函数又有什么区别吗?

```

``` 接下来我们再看一个demo:

我们创建另外一个类:

@interface Cat : NSObject

@property (assign, nonatomic) int weight;

@end

让Person类有一个cat对象。

@interface Person : NSObject

@property (assign, nonatomic) int age;

@property (assign, nonatomic) Cat *cat;

@end

以前我们想给person对象的cat对象的 weight属性赋值,我们该这样做。

Person *person = [[Person alloc] init];

Cat *cat = [[Cat alloc] init];

cat.weight = 10;

person.cat = cat;

那么现在有了

  • (void)setValue:(id)value forKeyPath:(NSString*)keyPath;

方法。我们就可以直接这样设置。

[person setValue:@10 forKeyPath:@"cat.weight"];

但是

  • (void)setValue:(id)value forKey:(NSString*)key;

就不可以这样写:

也就是说:

  • (void)setValue:(id)value forKeyPath:(NSString*)keyPath;

功能更加强大。

同样:我们访问person对象的cat对象

的weight属性。可以这样写。

NSLog(@"%@", [person valueForKeyPath:@"cat.weight"]);

而。

  • (id)valueForKey:(NSString*)key;

这个方法就不行。

更多的使用,自己可以写代码尝试下。

由于KVC也可以实现set方法类似的修改对象的属性。

那么,我们有这样一个疑问。

```

通过KVC修改属性会不会触发KVO?

怎么回答这个问题? 对!就是写代码测试一下。

怎么写测试代码? 一个person类,一个observer类。

让observer对象监听person对象的age属性变化。 注意:这一次改变

person对象的age属性时,我们使用KVC的方式修改age属性。并不用setAge:方法。

Observer类:

```

@interface Observer : NSObject @end

@implementation Observer

  • (void)observeValueForKeyPath:(NSString )keyPath ofObject:(id)object change:(NSDictionary )change context:(void *)context

{     NSLog(@"observeValueForKeyPath - %@", change); } @end

Person类: @interface Person : NSObject @property (assign, nonatomic) int age;

@end

测试: Observer observer = [[Observer alloc] init]; Person person = [[Person alloc] init];    

// 添加KVO监听

[person addObserver:observer forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:NULL];   

// 通过KVC修改age属性 [person setValue:@10 forKey:@"age"];

通过打断点及打印结果如下: observeValueForKeyPath - {     kind = 1;      new = 10;    old = 0 }

KVC为什么能触发KVO? 我们需要研究 setValue: forKey:方法。 [person setValue:@10 forKey:@"age"]; setValue:forKey:的原理:

屏幕快照 2021-10-13 下午11.10.45.png

如何证明: 可以 重写方法,增减成员变量等。来测试。。。。。

@interface Person : NSObject

{     @public //    int age; //    int isAge; //    int _isAge; //    int _age; }

@implementation Person

//- (void)setAge:(int)age //{ //    NSLog(@"setAge: - %d", age); //}

//- (void)_setAge:(int)age //{ //    NSLog(@"_setAge: - %d", age); //}

//- (void)willChangeValueForKey:(NSString )key //{ //    [super willChangeValueForKey:key]; //    NSLog(@"willChangeValueForKey - %@", key); //} // //- (void)didChangeValueForKey:(NSString )key //{ //    NSLog(@"didChangeValueForKey - begin - %@", key); //    [super didChangeValueForKey:key]; //  NSLog(@"didChangeValueForKey - end - %@", key); //}

// 默认的返回值就是YES //+ (BOOL)accessInstanceVariablesDirectly //{ //    return YES; //} @end

通过KVC修改属性会不会触发KVO?

会触发KVO。

之前的文章我们说直接修改成员变量不能够触发KVO. Person *person = [[Person alloc] init]; person->_age = 10;  // 直接修改成员变量,不能够触发KVO.

那通过KVC直接去修改_age成员变量时,也是可以触发KVO的。这是为什么?

KVC的赋值、取值过程是怎样的?原理是什么?

在Person类中重写

willChangeValueForKey:

didChangeValueForKey:

会发现,通过KVC直接修改age属性时,会调用上面的方法。

//可以猜测: 通过KVC修改age属性:

[person setValue:@10 forKey:@"age"];

赋值过程的内部调用:

[person willChangeValueForKey:@"age"];

person->_age = 10;

[person didChangeValueForKey:@"age"];

接下来看看取值过程的 内部调用:

valueForKey:的原理:

屏幕快照 2021-10-13 下午11.11.14.png

如何证明: 可以 重写方法,增减成员变量等。来测试。。。。。

@interface Person : NSObject {     @public //    int age; //    int isAge; //    int _isAge; //    int _age;

}

@implementation Person

//- (int)getAge //{ //    return 11; //}

//- (int)age //{ //    return 12; //}

//- (int)isAge //{ //    return 13; //}

//- (int)_age //{ //    return 14; //}

// 默认的返回值就是YES

//+ (BOOL)accessInstanceVariablesDirectly //{ //    return YES; //} @end

注意:setKey方法。。。。。。是调用KVO的关键方法。 ```