浅谈KVC的本质及原理

KVC全称是Key-Value Koding,俗称"键值编码",可以通过一个key访问某个属性.

常见的API有:

  • -(void)setValue:(nullable id)value forKey:(NSString *)key;
  • -(void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
  • - (nullable id)valueForKey:(NSString *)key;
  • - (nullable id)valueForKeyPath:(NSString *)keyPath;
❓通过KVC能否触发KVO?

我们通过这个问题开始研究KVC的本质?
我们创建一个HHPerson类,添加一个age属性,然后给这个age属性添加一个KVO,使用KVC修改age的值:

HHPerson *person = [[HHPerson alloc]init];
HHObserver *observer = [[HHObserver alloc]init];
[person addObserver:observer forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
[person setValue:@10 forKey:@"age"];

=================打印结果=================

2019-03-04 15:23:42.197654+0800 KVC_01[9905:1115065] age---{
    kind = 1;
    new = 10;
    old = 0;
}

可以看见,KVO触发了,之前我们讲过KVO的本质,知道触发KVO的关键在于重写了setter方法,既然KVC也能触发KVO,那就说明KVC也调用了setAge:方法,我们重写setAge:方法,打印一句话看看:

- (void)setAge:(int)age{
    _age = age;
    NSLog(@"setAge方法被调用");
}

=================打印结果=================

2019-03-04 15:28:24.318732+0800 KVC_01[9927:1116525] setAge方法被调用
2019-03-04 15:28:24.319088+0800 KVC_01[9927:1116525] age---{
    kind = 1;
    new = 10;
    old = 0;
}

可以看到KVC访问属性的时候,的确调用了setAge:方法.那么setVlaue:forKey:是如何设值的呢?
我用一张图来演示setVlaue:forKey:的执行顺序:

浅谈KVC的本质及原理_第1张图片
KVC赋值流程

执行步骤:
1:setVlaue:forKey:首先查找setKey:方法,如果有就执行;如果没有再去查找_setKey:方法.
2:如果没有找到_setKey:方法,就去查看accessInstanceVariablesDirectly方法的返回值,这个方法的意思是:是否允许直接进入对象的成员变量?,如果返回YES,就按照_key,_isKey,key,isKey的顺序查找成员变量,如果找到直接赋值;如果没找到就抛出异常NSUnknownKeyException.
3:如果accessInstanceVariablesDirectly方法直接返回NO(默认返回YES),就直接抛出异常NSUnknownKeyException.

我们先证明第一点,把HHPerson类中属性删掉,然后重写setAge:,_setAge:方法:

- (void)setAge:(int)age{
    NSLog(@"setAge方法被调用");
}

- (void)_setAge:(int)age{
    NSLog(@"_setAge被调用");
}

然后使用KVC给age赋值,注意:此时HHPerson中已经没有属性age:

HHPerson *person = [[HHPerson alloc]init];
[person setValue:@10 forKey:@"age"];

========================打印结果====================

2019-03-04 16:09:39.886100+0800 KVC_01[10105:1130147] setAge方法被调用

我们把setAge:方法注释掉,再运行:

2019-03-04 16:10:18.558353+0800 KVC_01[10115:1130494] _setAge被调用

通过运行结果可以很清楚的看到,[person setValue:@10 forKey:@"age"];会先去查找setAge:方法,如果有就调用,如果没有再去查找_setAge:方法.

接下来验证第二点,我们在HHPerson注释掉刚才的setAge:方法,然后重写accessInstanceVariablesDirectly方法,然后返回NO,运行下看看效果:

浅谈KVC的本质及原理_第2张图片

再修改为return YES;然后添加如下成员变量:

@interface HHPerson : NSObject
{
    @public
    int _age;
    int _isAge;
    int age;
    int isAge;
}
@end

在运行一下看看结果:


浅谈KVC的本质及原理_第3张图片

有人可能会以为是成员变量顺序导致的,我们把成员变量的顺序打乱在执行看看:

@interface HHPerson : NSObject
{
    @public
    int age;
    int _isAge;
    int isAge;
    int _age;
}
@end
浅谈KVC的本质及原理_第4张图片

我们把4个属性全注释掉,运行一下看看效果:


又报 NSUnknownKeyException的错误.

我们之前讲过:如果修改一个类的成员变量,会不会触发KVO?
答案是:不会!
但是如果用KVC修改一个类的成员变量就会触发KVO.我们来验证一下:

// HHPerson 中只有一个成员变量 age
@interface HHPerson : NSObject
{
    @public
    int age;
//    int _isAge;
//    int isAge;
//    int _age;
}
@end

// 使用 KVC 修改这个 age 的值
HHPerson *person = [[HHPerson alloc]init];
HHObserver *observer = [[HHObserver alloc]init];
[person addObserver:observer forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
[person setValue:@10 forKey:@"age"];

==================打印结果====================

2019-03-04 17:04:15.930503+0800 KVC_01[10255:1148495] age---{
    kind = 1;
    new = 10;
    old = 0;
}

可以看到的确触发 KVO 了.这是为什么呢?
因为KVC赋值同样会调用willChangeValueForKey:,didChangeValueForKey:两个方法,我们在HHPerson中重写这两个方法:

- (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);
}


==================打印结果====================

2019-03-04 17:08:31.074916+0800 KVC_01[10292:1150309] willChangeValueForKey:age
2019-03-04 17:08:31.075122+0800 KVC_01[10292:1150309] didChangeValueForKey:age -----begin
2019-03-04 17:08:31.075304+0800 KVC_01[10292:1150309] age---{
    kind = 1;
    new = 10;
    old = 0;
}

可以看到,这两个方法的确都被调用了,所以KVC给一个类的属性赋值也能触发KVO.

以上讲的都是KVC的设值方法,接下来我们讲解一下KVC的取值方法.其实跟设值方法过程差不多,我们也用一张图表示:


浅谈KVC的本质及原理_第5张图片
KVC取值流程

大家可以参照上面的赋值流程验证一下,我就不重复验证了,都差不多.

❓KVC的赋值过程和取值过程是怎样的?原理是什么?
答案:上面两张流程图

你可能感兴趣的:(浅谈KVC的本质及原理)