KVO-KVC的原理探究 - KVC篇

關(guān)于KVC的探究

基本介紹和使用

KVC全稱(chēng)Key-Value Coding 鍵值編碼,可以通過(guò)Key來(lái)訪(fǎng)問(wèn)某個(gè)屬性诲祸,常見(jiàn)的API:


- (nullable id)valueForKeyPath:(NSString *)keyPath;

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

- (nullable id)valueForKey:(NSString *)key;

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

創(chuàng)建Person類(lèi)、Animal類(lèi),添加屬性如下:


@interface Animal : NSObject

@property (nonatomic, assign) NSInteger height;

@end

@interface Person : NSObject

@property (nonatomic, assign) NSInteger age;

@property (nonatomic, strong) Animal * dog;

@end

如上拐叉,使用KVC賦值的方式為:


Animal * dog = [[Animal alloc] init];

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

person.dog = dog;

// Key        

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

// KeyPath

[person setValue:@123 forKeyPath:@"dog.height"];

NSLog(@"---- %@ -- %@", @(person.age), @(person.dog.height));

打印結(jié)果:

2018-07-19 23:00:48.546719+0800 KVC[53839:3059207] ---- 11 -- 123

可以看到直接賦值的話(huà)兩中方式都可以,但是類(lèi)似上面的dog.height嵌套的方式必須通過(guò)KeyPath的方式賦值扇商。

KVC原理

進(jìn)入Foundation里面查看 - (void)setValue:(nullable id)value forKey:(NSString *)key; 方法的注解可以了解到凤瘦,KVC的賦值步驟 如下圖:


st=>start: 調(diào)用setValue:forKey

e_findSucceed=>end: 傳遞參數(shù),調(diào)用方法

op1=>operation: Operation1

sub1=>subroutine: My Subroutine

cond_findMethod=>condition: 按照 setKey:

_setKey:

順序查找方法

cond_instanceVariables=>condition: 查看

accessInstance

VariablesDirectly

(默認(rèn)返回YES)

方法的返回值

cond_findIvar=>condition: 按照

_key案铺、_isKey

key蔬芥、isKey

順序查找

成員變量

e_findIvar=>end: 直接賦值

e_exception=>end: 調(diào)用

setValue:forUndefinedKey:

并且拋出

NSUnKnownKeyException

st->cond_findMethod

cond_findMethod(yes, bottom)->e_findSucceed

cond_findMethod(no, right)->cond_instanceVariables

cond_instanceVariables(yes, bottom)->cond_findIvar

cond_findIvar(yes, bottom)->e_findIvar

cond_instanceVariables(no, right)->e_exception

cond_findIvar(no, right)->e_exception

以上為markdown語(yǔ)法,用的 MWebLite編寫(xiě)的控汉,奈何blog不支持笔诵,所以直接將結(jié)果導(dǎo)圖截圖貼在下面:

image

進(jìn)入Foundation里面查看 - (nullable id)valueForKey:(NSString *)key; 方法的注解可以了解到,KVC的取值步驟 如下圖:


st=>start: 調(diào)用valueForKey:

e_findSucceed=>end: 調(diào)用方法

op1=>operation: Operation1

sub1=>subroutine: My Subroutine

cond_findMethod=>condition: 按照 getKey

key姑子、isKey乎婿、

_key、_getKey

順序查找方法

cond_instanceVariables=>condition: 查看

accessInstance

VariablesDirectly

(默認(rèn)返回YES)

方法的返回值

cond_findIvar=>condition: 按照

_key街佑、_isKey

key谢翎、isKey

順序查找

成員變量

e_findIvar=>end: 直接取值

e_exception=>end: 調(diào)用

valueForUndefinedKey:

并且拋出

NSUnKnownKeyException

st->cond_findMethod

cond_findMethod(yes, bottom)->e_findSucceed

cond_findMethod(no, right)->cond_instanceVariables

cond_instanceVariables(yes, bottom)->cond_findIvar

cond_findIvar(yes, bottom)->e_findIvar

cond_instanceVariables(no, right)->e_exception

cond_findIvar(no, right)->e_exception

image

接下來(lái)我們來(lái)驗(yàn)證一下:

setValue:forKey:賦值

使用上面的Person類(lèi),將屬性全部刪除沐旨,添加以下成員變量森逮,重寫(xiě)兩個(gè)set方法:


@interface Person : NSObject {

 @public

 int _age;

 int _isAge;

 int age;

 int isAge;

}

@end

- (void)setAge:(NSInteger)age {

 NSLog(@"--- setAge");

}

- (void)_setAge:(NSInteger)age {

 NSLog(@"--- _setAge");

}

然后調(diào)用KVC給age賦值,可以看打印結(jié)果:


2018-07-20 14:41:18.679275+0800 KVC[65810:3455316] --- setAge

此時(shí)調(diào)用的是 setAge: 方法磁携,注釋掉 setAge: 方法再次運(yùn)行:


2018-07-20 14:41:43.561291+0800 KVC[65834:3456053] --- _setAge

當(dāng)這兩個(gè)方法都沒(méi)有實(shí)現(xiàn)的時(shí)候就會(huì)調(diào)用 accessInstanceVariablesDirectly 方法褒侧,若返回YES,則直接給成員變量賦值,如沒(méi)有或返回值為NO則會(huì)調(diào)用 setValue:forUndefinedKey: 并且拋出NSUnKnownKeyException異常璃搜。

valueForKey:取值

重寫(xiě)流程圖中的get方法:


- (int)getAge {

 return 11;

}

- (int)age {

 return 12;

}

- (int)isAge {

 return 13;

}

- (int)_age {

 return 14;

}

- (int)_getAge {

 return 15;

}

打印結(jié)果:

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

如上我們可以查看當(dāng)調(diào)用過(guò)的方法后就注釋?zhuān)钡綀?zhí)行完成所有的方法拖吼。我們可以控制 accessInstanceVariablesDirectly 方法的返回值來(lái)證明我們想要的結(jié)果。

思考一下KVC能觸發(fā)KVO嗎这吻?

我們來(lái)測(cè)試一下吊档,添加Observer類(lèi)來(lái)監(jiān)聽(tīng)并輸出Log:


@implementation Observer

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey, id> *)change context:(void *)context {

 NSLog(@"observeValueForKeyPath - %@", change);

}

@end

測(cè)試代碼:

Observer * ob = [Observer new];

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

[person addObserver:ob forKeyPath:@"age" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:nil];

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

[person removeObserver:ob forKeyPath:@"age"];

打印結(jié)果為:

2018-07-20 15:19:30.526730+0800 KVC[67145:3507833] observeValueForKeyPath - {

 kind = 1;

 new = 1;

 old = 0;

}

可以看到KVC確實(shí)調(diào)用了KVO,上一篇文章中我們了解到了KVO的實(shí)現(xiàn)唾糯,接下來(lái)可以大概驗(yàn)證一下:


在Person類(lèi)中實(shí)現(xiàn)如下方法:

- (void)willChangeValueForKey:(NSString *)key {

 NSLog(@"willChangeValueForKey");

 [super willChangeValueForKey:key];

}

- (void)didChangeValueForKey:(NSString *)key {

 NSLog(@"didChangeValueForKey-- begin");

 [super didChangeValueForKey:key];

 NSLog(@"didChangeValueForKey-- end");

}

打印結(jié)果:

2018-07-20 15:23:34.761496+0800 KVC[67256:3513685] willChangeValueForKey

2018-07-20 15:23:34.761836+0800 KVC[67256:3513685] didChangeValueForKey-- begin

2018-07-20 15:23:34.762151+0800 KVC[67256:3513685] observeValueForKeyPath - {

 kind = 1;

 new = 1;

 old = 0;

}

2018-07-20 15:23:34.762202+0800 KVC[67256:3513685] didChangeValueForKey-- end

可以看到打印結(jié)果跟KVO是一樣的怠硼,這里可以猜測(cè)蘋(píng)果大大在KVC內(nèi)部的實(shí)現(xiàn):


[person willChangeValueForKey:@"age"];

person->_age = 1;

[person didChangeValueForKey:@"age"];

KVC相當(dāng)于手動(dòng)調(diào)用了KVO。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末移怯,一起剝皮案震驚了整個(gè)濱河市香璃,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌舟误,老刑警劉巖葡秒,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異嵌溢,居然都是意外死亡眯牧,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)赖草,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)学少,“玉大人,你說(shuō)我怎么就攤上這事秧骑“嫒罚” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵乎折,是天一觀(guān)的道長(zhǎng)绒疗。 經(jīng)常有香客問(wèn)我,道長(zhǎng)笆檀,這世上最難降的妖魔是什么忌堂? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任盒至,我火速辦了婚禮酗洒,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘枷遂。我一直安慰自己樱衷,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布酒唉。 她就那樣靜靜地躺著矩桂,像睡著了一般。 火紅的嫁衣襯著肌膚如雪痪伦。 梳的紋絲不亂的頭發(fā)上侄榴,一...
    開(kāi)封第一講書(shū)人閱讀 49,144評(píng)論 1 285
  • 那天雹锣,我揣著相機(jī)與錄音,去河邊找鬼癞蚕。 笑死蕊爵,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的桦山。 我是一名探鬼主播攒射,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼恒水!你這毒婦竟也來(lái)了会放?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤钉凌,失蹤者是張志新(化名)和其女友劉穎咧最,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體御雕,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡窗市,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了饮笛。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片咨察。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖福青,靈堂內(nèi)的尸體忽然破棺而出摄狱,到底是詐尸還是另有隱情,我是刑警寧澤无午,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布媒役,位于F島的核電站,受9級(jí)特大地震影響宪迟,放射性物質(zhì)發(fā)生泄漏酣衷。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一次泽、第九天 我趴在偏房一處隱蔽的房頂上張望穿仪。 院中可真熱鬧,春花似錦意荤、人聲如沸啊片。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)紫谷。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間笤昨,已是汗流浹背祖驱。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留瞒窒,地道東北人羹膳。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像根竿,于是被迫代替她去往敵國(guó)和親陵像。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345