KVC全稱是Key-Value Coding,俗稱"鍵值編碼",可以通過一個(gè)key訪問某個(gè)屬性.
常見的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能否觸發(fā)KVO?
我們通過這個(gè)問題開始研究KVC的本質(zhì)?
我們創(chuàng)建一個(gè)HHPerson類,添加一個(gè)age屬性,然后給這個(gè)age屬性添加一個(gè)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"];
=================打印結(jié)果=================
2019-03-04 15:23:42.197654+0800 KVC_01[9905:1115065] age---{
kind = 1;
new = 10;
old = 0;
}
可以看見,KVO觸發(fā)了,之前我們講過KVO的本質(zhì),知道觸發(fā)KVO的關(guān)鍵在于重寫了setter方法,既然KVC也能觸發(fā)KVO,那就說明KVC也調(diào)用了setAge:方法,我們重寫setAge:方法,打印一句話看看:
- (void)setAge:(int)age{
_age = age;
NSLog(@"setAge方法被調(diào)用");
}
=================打印結(jié)果=================
2019-03-04 15:28:24.318732+0800 KVC_01[9927:1116525] setAge方法被調(diào)用
2019-03-04 15:28:24.319088+0800 KVC_01[9927:1116525] age---{
kind = 1;
new = 10;
old = 0;
}
可以看到KVC訪問屬性的時(shí)候,的確調(diào)用了setAge:方法.那么setVlaue:forKey:是如何設(shè)值的呢?
我用一張圖來演示setVlaue:forKey:的執(zhí)行順序:

執(zhí)行步驟:
1:setVlaue:forKey:首先查找setKey:方法,如果有就執(zhí)行;如果沒有再去查找_setKey:方法.
2:如果沒有找到_setKey:方法,就去查看accessInstanceVariablesDirectly方法的返回值,這個(gè)方法的意思是:是否允許直接進(jìn)入對象的成員變量?,如果返回YES,就按照_key,_isKey,key,isKey的順序查找成員變量,如果找到直接賦值;如果沒找到就拋出異常NSUnknownKeyException.
3:如果accessInstanceVariablesDirectly方法直接返回NO(默認(rèn)返回YES),就直接拋出異常NSUnknownKeyException.
我們先證明第一點(diǎn),把HHPerson類中屬性刪掉,然后重寫setAge:,_setAge:方法:
- (void)setAge:(int)age{
NSLog(@"setAge方法被調(diào)用");
}
- (void)_setAge:(int)age{
NSLog(@"_setAge被調(diào)用");
}
然后使用KVC給age賦值,注意:此時(shí)HHPerson中已經(jīng)沒有屬性age:
HHPerson *person = [[HHPerson alloc]init];
[person setValue:@10 forKey:@"age"];
========================打印結(jié)果====================
2019-03-04 16:09:39.886100+0800 KVC_01[10105:1130147] setAge方法被調(diào)用
我們把setAge:方法注釋掉,再運(yùn)行:
2019-03-04 16:10:18.558353+0800 KVC_01[10115:1130494] _setAge被調(diào)用
通過運(yùn)行結(jié)果可以很清楚的看到,[person setValue:@10 forKey:@"age"];會先去查找setAge:方法,如果有就調(diào)用,如果沒有再去查找_setAge:方法.
接下來驗(yàn)證第二點(diǎn),我們在HHPerson注釋掉剛才的setAge:方法,然后重寫accessInstanceVariablesDirectly方法,然后返回NO,運(yùn)行下看看效果:

再修改為return YES;然后添加如下成員變量:
@interface HHPerson : NSObject
{
@public
int _age;
int _isAge;
int age;
int isAge;
}
@end
在運(yùn)行一下看看結(jié)果:

有人可能會以為是成員變量順序?qū)е碌?我們把成員變量的順序打亂在執(zhí)行看看:
@interface HHPerson : NSObject
{
@public
int age;
int _isAge;
int isAge;
int _age;
}
@end

我們把4個(gè)屬性全注釋掉,運(yùn)行一下看看效果:

又報(bào)
NSUnknownKeyException的錯(cuò)誤.
我們之前講過:如果修改一個(gè)類的成員變量,會不會觸發(fā)KVO?
答案是:不會!
但是如果用KVC修改一個(gè)類的成員變量就會觸發(fā)KVO.我們來驗(yàn)證一下:
// HHPerson 中只有一個(gè)成員變量 age
@interface HHPerson : NSObject
{
@public
int age;
// int _isAge;
// int isAge;
// int _age;
}
@end
// 使用 KVC 修改這個(gè) 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"];
==================打印結(jié)果====================
2019-03-04 17:04:15.930503+0800 KVC_01[10255:1148495] age---{
kind = 1;
new = 10;
old = 0;
}
可以看到的確觸發(fā) KVO 了.這是為什么呢?
因?yàn)镵VC賦值同樣會調(diào)用willChangeValueForKey:,didChangeValueForKey:兩個(gè)方法,我們在HHPerson中重寫這兩個(gè)方法:
- (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);
}
==================打印結(jié)果====================
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;
}
可以看到,這兩個(gè)方法的確都被調(diào)用了,所以KVC給一個(gè)類的成員變量賦值也能觸發(fā)KVO.
以上講的都是KVC的設(shè)值方法,接下來我們講解一下KVC的取值方法.其實(shí)跟設(shè)值方法過程差不多,我們也用一張圖表示:

大家可以參照上面的賦值流程驗(yàn)證一下,我就不重復(fù)驗(yàn)證了,都差不多.
?KVC的賦值過程和取值過程是怎樣的?原理是什么?
答案:上面兩張流程圖