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中的異常
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"];