KVC的實現原理

KVC是Key Value Coding的簡稱。它是一種可以通過字符串的名字(key)來訪問類屬性的機制呻征。而不是通過調用Setter谆级、Getter方法訪問。KVC的方法定義在Foundation/NSKeyValueCoding中扯罐。

KVC使用的基本方法:

- (nullable id)valueForKey:(NSString*)key;//直接通過Key來取值

- (void)setValue:(nullable id)value forKey:(NSString*)key;//通過Key來設值

- (nullable id)valueForKeyPath:(NSString*)keyPath;//通過KeyPath來取值

- (void)setValue:(nullable id)value forKeyPath:(NSString*)keyPath;//通過KeyPath來設值

//默認返回YES负拟,表示如果沒有找到Set方法的話,會按照_key歹河,_iskey掩浙,key,iskey的順序搜索成員秸歧,設置成NO就不這樣搜索

+ (BOOL)accessInstanceVariablesDirectly;?

//KVC提供屬性值正確性驗證的API厨姚,它可以用來檢查set的值是否正確、為不正確的值做一個替換值或者拒絕設置新值并返回錯誤原因键菱。?- (BOOL)validateValue:(inout id __nullable * __nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;

?//如果Key不存在谬墙,且沒有KVC無法搜索到任何和Key有關的字段或者屬性,則會調用這個方法经备,默認是拋出異常拭抬。

?- (nullable id)valueForUndefinedKey:(NSString *)key;

- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;//和上一個方法一樣,但這個方法是設值侵蒙。

- (void)setNilValueForKey:(NSString *)key;//如果你在SetValue方法時面給Value傳nil造虎,則會調用這個方法

- (NSDictionary *)dictionaryWithValuesForKeys:(NSArray *)keys;//輸入一組key,返回該組key對應的Value,再轉成字典返回蘑志,用于將Model轉到字典累奈。 ?

設值的實現步驟:

1.首先搜索是否有setKey:的方法(key是成員變量名贬派,首字母大寫),沒有則會搜索是否有setIsKey:的方法。

2.如果沒有找到setKey:的方法,此時看+ (BOOL)accessInstanceVariablesDirectly; (是否直接訪問成員變量)方法澎媒。

若返回NO搞乏,則直接調用- (nullable id)valueForUndefinedKey:;(默認是拋出異常)。

若返回YES戒努,按 _key请敦、_iskey、key储玫、isKey的順序搜索成員名侍筛。

3.在第二步還沒搜到的話就會調用- (nullable id)valueForUndefinedKey:方法。

驗證一:如果實現setKey:方法則不會調用_setKey:和+ (BOOL)accessInstanceVariablesDirectly; 方法

創(chuàng)建一個類YYKVCModel撒穷,不聲明屬性匣椰,在.m文件同時實現以下三個方法:

+ (BOOL)accessInstanceVariablesDirectly{

? ? NSLog(@"accessInstanceVariablesDirectly");

? ? return YES;

}

- (void)_setTestName:(NSString *)testName{? ?

????NSLog(@"不被調用,因為實現了setTestName\n");

}

- (void)setTestName:(NSString *)testName{?

? ? ?NSLog(@"setTestName調用\n");

}

用kvc賦值

YYKVCModel *model = [[YYKVCModel alloc] init];

?[model setValue:@"1223" forKey:@"testName"];

運行結果:只調用了setTestName方法端礼。說明setTestName方法優(yōu)先級高于_setTestName方法禽笑。

驗證二:注釋掉setTestName:方法,新增方法setIsTestName:

+ (BOOL)accessInstanceVariablesDirectly{

? NSLog(@"accessInstanceVariablesDirectly");

? ? return YES;

}

- (void)_setTestName:(NSString *)testName{?

????NSLog(@"_setTestName被調用\n");

}

- (void)setIsTestName:(NSString *)testName{ ?

? ? ?NSLog(@"setIsTestName不被調用\n");

}

運行結果:調用了_setTestName方法蛤奥,不調用setIsTestName方法佳镜。說明_setTestName的優(yōu)先級高于setIsTestName。

驗證三:注釋掉_setTestName:方法添加成員變量

@interface YYKVCModel : NSObject {

? ? NSString *_testName;

}

運行結果:調用了setIsTestName:方法凡桥,_testName的值為null蟀伸。說明setIsTestName方法優(yōu)先級高于直接給_testName賦值。

驗證四:注釋掉setIsTestName:方法缅刽,為YYKVCModel增加以下成員變量

@interface YYKVCModel : NSObject {

? ? NSString *_testName;

? ? NSString *_isTestName;

}

運行結果:+ (BOOL)accessInstanceVariablesDirectly方法被調用啊掏,此時返回的是YES。_testName值變?yōu)?223拷恨,_isTestName值為null脖律。說明直接給_testName賦值優(yōu)先級高于直接給_isTestName賦值谢肾。

驗證五:為YYKVCModel增加以下成員變量

@interface YYKVCModel : NSObject {

? ? NSString *_isTestName;

? ? NSString *testName;

}

運行結果:+ (BOOL)accessInstanceVariablesDirectly方法被調用腕侄,此時返回的是YES。_isTestName值變?yōu)?223芦疏,testName值為null冕杠。說明直接給_isTestName賦值優(yōu)先級高于直接給testName賦值。

驗證六:為YYKVCModel增加以下成員變量

@interface YYKVCModel : NSObject {

? ? NSString *testName;

? ? NSString *isTestName;

}

運行結果:+ (BOOL)accessInstanceVariablesDirectly方法被調用酸茴,此時返回的是YES分预。testName值變?yōu)?223,isTestName值為null薪捍。說明直接給testName賦值優(yōu)先級高于直接給isTestName賦值笼痹。

驗證七:注釋?NSString *testName;

運行結果:+ (BOOL)accessInstanceVariablesDirectly方法被調用配喳,此時返回的是YES。isTestName值變?yōu)?223凳干。

驗證八:在+ (BOOL)accessInstanceVariablesDirectly方法返回NO

運行結果:調用兩次+ (BOOL)accessInstanceVariablesDirectly方法晴裹,然后調用- (void)setValue:(id)value forUndefinedKey:(NSString *)key方法,不實現方法將默認拋出異常救赐,isTestName值為null涧团。

若返回YES則只會調用一次。

由以上實驗得出結論:

以上的代碼中并沒有聲明testName這個屬性经磅,若聲明屬性會默認生成setter和getter方法泌绣,無法進行測試。

KVC的賦值本質上只是調用了屬性的setter方法预厌,setter方法會按照setKey阿迈、_setKey、setIsKey的優(yōu)先級進行調用轧叽,還沒有仿滔,則按_key、_isKey犹芹、key崎页、isKey查找成員變量。

如果accessInstanceVariablesDirectly返回NO腰埂,則不會查找_key飒焦、_isKey、key屿笼、isKey牺荠,會直接調用- (void)setValue:(id)value forUndefinedKey:(NSString *)key。

若查找到isKey還是沒找到驴一,也會調用(void)setValue:(id)value forUndefinedKey:(NSString *)key休雌,該方法默認會拋出異常。

KVC取值的實現:

1.按先后順序搜索getKey肝断、key杈曲、isKey、_getKey胸懈、_key五個方法担扑,若某一個方法被實現,取到的即是方法返回的值趣钱,后面的方法不再運行涌献。如果是BOOL或者Int等值類型, 會將其包裝成一個NSNumber對象首有。

2.若這五個方法都沒有找到燕垃,則會調用+ (BOOL)accessInstanceVariablesDirectly方法判斷是否允許取成員變量的值枢劝。

若返回NO,直接調用- (nullable id)valueForUndefinedKey:(NSString *)key方法卜壕,默認是奔潰呈野。

若返回YES,會按先后順序取_key、_isKey印叁、 key被冒、isKey的值。

3.返回YES時轮蜕,_key昨悼、_isKey、 key跃洛、isKey的值都沒取到率触,調用- (nullable id)valueForUndefinedKey:(NSString *)key方法。

驗證方法與上面設置值類似汇竭。

驗證后得出結論:

在實驗中同樣沒有用屬性聲明testName葱蝗。在取值過程中按順序看getKey、key细燎、isKey两曼、_getKey、_key五個方法是否實現玻驻,若實現了則取到的值為方法返回的值悼凑,所以本質上是按先后順序調用了這五個getter方法,如果沒有璧瞬,則會詢問+ (BOOL)accessInstanceVariablesDirectly方法能否直接取成員變量户辫,若返回YES,則會按順序取_key嗤锉、_isKey渔欢、 key、isKey的值瘟忱。

如果在+ (BOOL)accessInstanceVariablesDirectly中返回NO奥额,或者取到isKey仍然取不到值,則會調用- (nullable id)valueForUndefinedKey方法酷誓,該方法中返回的值即為取到的值披坏。

KVC取值的補充:

在取值的第一步結束第二步開始之前,還會判斷獲取的值是否是數組或者集合盐数。

1.如果是數組(NSArray *)的話,當實現countOf方法和objectInAtIndex或AtIndexes中任意一個方法時則會返回一個數組.

具體寫法:key為testArray

-(NSUInteger)countOfTestArray{

? ? return 3;

}

- (id)testArrayAtIndexes:(NSIndexSet *)indexs{

? ? return @[@(2), @(4), @(6)];

}

-(id)objectInTestArrayAtIndex:(NSUInteger)index{ ? ?

? ? return @(index);

}

KVC調用

YYKVCModel *model = [[YYKVCModel alloc] init];

id array = [model valueForKey:@"testArray"]; array類型為NSKeyValueArray

2.不可變數組還可以實現get<Key>:range:

如果是可變數組還可以實現:

-insertObject:in<Key>AtIndex:或者-insert<Key>:atIndexes:

-removeObjectFrom<Key>AtIndex:或者-remove<Key>AtIndexes:

-replaceObjectIn<Key>AtIndex:withObject:或者-replace<Key>AtIndexes:with<Key>:

3.集合需要實現以下三個方法:

-countOf<Key>

-enumeratorOf<Key>

-memberOf:<key>

屬性是集合和數組的實現

KVC

KVC中的異常

1.獲取值時找不到key

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

2.設值時找不到key

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

3.給不能設置nil的屬性設置了nil伞梯。

定義一個屬性

@property (nonatomic, assign) NSInteger num;

用kvc賦值

YYKVCModel *model = [[YYKVCModel alloc] init];

[model setValue:nil forKey:@"num"];

此時運行的話程序會崩潰玫氢,報錯如下

重寫- (void)setNilValueForKey:(NSString *)key方法后會發(fā)現不再奔潰帚屉,可知在該方法中默認拋出了異常。我們可以重寫該方法做處理漾峡。

KVC處理非對象和自定義對象

KVC中返回的是一個id類型的對象攻旦,所以調用valueForKey:時如果是基本數據類型或者結構體,KVC會自動轉成NSNumber類型或者NSValue類型生逸,但是調用SetValue: forKey:時需要手動把基本數據類型或者結構體轉成對象牢屋。

自定義對象需要我們自己保證類型的正確性。

KVC的屬性驗證

- (BOOL)validateValue:(inout id _Nullable __autoreleasing *)ioValue forKey:(NSString *)inKey error:(out NSError * _Nullable __autoreleasing *)outError;對應使用key的方式槽袄。

- (BOOL)validateValue:(inout id _Nullable __autoreleasing *)ioValue forKeyPath:(NSString *)inKeyPath error:(out NSError * _Nullable __autoreleasing *)outError;對應使用keyPath的方式烙无。

KVC并不會自動調用該方法,需要我們手動調用

比如在設置值的時候我們可以判斷鍵值的正確性遍尺,如果正確則繼續(xù)操作截酷,失敗則不操作。驗證失敗后可以把錯誤存放在outError返回乾戏,這邊的ioValue傳入的也是指針迂苛,所以可以在需要時在驗證方法中更改value,然后在外面設置鼓择。

KVC與容器類

?在使用KVO觀察屬性改變時三幻,會發(fā)現如果觀察可變數組時,對于添加或者移除元素時并不能接收到變化呐能。因為KVO的本質是在setter方法上赌髓,添加上- (void)willChangeValueForKey:(NSString *)key和- (void)didChangeValueForKey:(NSString *)key方法來發(fā)送通知。此時用mutableArrayValueForKey:方法獲取數組催跪,在做增加刪除操作就能接收到監(jiān)聽锁蠕。

寫法如:

[[self mutableArrayValueForKey:@"mutableArray"] addObject:@"1"];

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市懊蒸,隨后出現的幾起案子荣倾,更是在濱河造成了極大的恐慌,老刑警劉巖骑丸,帶你破解...
    沈念sama閱讀 222,000評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件舌仍,死亡現場離奇詭異,居然都是意外死亡通危,警方通過查閱死者的電腦和手機铸豁,發(fā)現死者居然都...
    沈念sama閱讀 94,745評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來菊碟,“玉大人节芥,你說我怎么就攤上這事。” “怎么了头镊?”我有些...
    開封第一講書人閱讀 168,561評論 0 360
  • 文/不壞的土叔 我叫張陵蚣驼,是天一觀的道長。 經常有香客問我相艇,道長颖杏,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,782評論 1 298
  • 正文 為了忘掉前任坛芽,我火速辦了婚禮留储,結果婚禮上,老公的妹妹穿的比我還像新娘咙轩。我一直安慰自己获讳,他們只是感情好,可當我...
    茶點故事閱讀 68,798評論 6 397
  • 文/花漫 我一把揭開白布臭墨。 她就那樣靜靜地躺著赔嚎,像睡著了一般。 火紅的嫁衣襯著肌膚如雪胧弛。 梳的紋絲不亂的頭發(fā)上尤误,一...
    開封第一講書人閱讀 52,394評論 1 310
  • 那天,我揣著相機與錄音损晤,去河邊找鬼。 笑死红竭,一個胖子當著我的面吹牛尤勋,可吹牛的內容都是我干的。 我是一名探鬼主播茵宪,決...
    沈念sama閱讀 40,952評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼最冰,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了稀火?” 一聲冷哼從身側響起暖哨,我...
    開封第一講書人閱讀 39,852評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎凰狞,沒想到半個月后篇裁,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 46,409評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,483評論 3 341
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了骇窍。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,615評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡黍聂,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情分冈,我是刑警寧澤圾另,帶...
    沈念sama閱讀 36,303評論 5 350
  • 正文 年R本政府宣布霸株,位于F島的核電站雕沉,受9級特大地震影響,放射性物質發(fā)生泄漏去件。R本人自食惡果不足惜坡椒,卻給世界環(huán)境...
    茶點故事閱讀 41,979評論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望尤溜。 院中可真熱鬧倔叼,春花似錦、人聲如沸宫莱。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,470評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽授霸。三九已至巡验,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間碘耳,已是汗流浹背显设。 一陣腳步聲響...
    開封第一講書人閱讀 33,571評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留辛辨,地道東北人捕捂。 一個月前我還...
    沈念sama閱讀 49,041評論 3 377
  • 正文 我出身青樓,卻偏偏與公主長得像斗搞,于是被迫代替她去往敵國和親指攒。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,630評論 2 359

推薦閱讀更多精彩內容

  • KVC(Key-value coding)鍵值編碼僻焚,單看這個名字可能不太好理解允悦。其實翻譯一下就很簡單了,就是指iO...
    朽木自雕也閱讀 1,563評論 6 1
  • KVC KVC定義 KVC(Key-value coding)鍵值編碼溅呢,就是指iOS的開發(fā)中澡屡,可以允許開發(fā)者通過K...
    暮年古稀ZC閱讀 2,148評論 2 9
  • 這兩天晚上在學習騰訊公開課的視頻,其中在1月18號的內容是KVC的底層實現咐旧∈火模看完后簡單記一下學習筆記。 1铣墨、[ob...
    ZzS_2d89閱讀 556評論 0 1
  • 一室埋、KVO的實現原理 KVO的全稱是Key-ValueObserving(鍵值監(jiān)聽),可以用于監(jiān)聽某個對象屬性值的...
    王的for閱讀 1,054評論 1 0
  • KVC(Key-value coding)鍵值編碼,單看這個名字可能不太好理解姚淆。其實翻譯一下就很簡單了孕蝉,就是指iO...
    我的夢工廠閱讀 891評論 1 8