淺談KVC的本質(zhì)及原理

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í)行順序:

KVC賦值流程

執(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è)值方法過程差不多,我們也用一張圖表示:


KVC取值流程

大家可以參照上面的賦值流程驗(yàn)證一下,我就不重復(fù)驗(yàn)證了,都差不多.

?KVC的賦值過程和取值過程是怎樣的?原理是什么?
答案:上面兩張流程圖

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容