-
About key-value coding(關(guān)于KVC)
關(guān)鍵值編碼是一種機制,使非正式協(xié)議NSKeyValueCoding對象采取提供間接訪問性能,當(dāng)一個對象遵守鍵值編碼時,它的屬性可以通過字符串參數(shù)通過簡明的方式進行尋址,這種間接訪問機制也可以直接訪問實例變量
你通常使用訪問器方法訪問一個對象的屬性。一個get訪問器(或getter方法)返回一個屬性的值。set訪問器(或setter方法)設(shè)置一個屬性的值。在Objective-C中,你也可以直接訪問屬性的實例變量。以任何這些方式訪問對象屬性都很簡單,但需要調(diào)用特定于屬性的方法或變量名,隨著屬性列表的增長或變化,訪問這些屬性的代碼也必須隨之改變。與此相反,鍵值兼容的對象提供了一個簡單的消息傳遞接口,它的所有屬性都是一致的
key-value coding 也是其它一些技術(shù)的基本概念,比如:kvo,core data......
-
Using Key-Value Coding Compliant Objects(使用鍵值對兼容對象)
一個對象如果是直接或者間接繼承于NSObject,
并且都遵守NSKeyValueCoding 協(xié)議和一些基本方法的實現(xiàn)(這句話的意思如下,其實nsobject已經(jīng)遵守了NSKeyValueCoding協(xié)議)
所以其實簡單的來說只要滿足第一個條件它就有kvc的功能
-
Key-Value Coding Fundamentals(KVC的基本概念)
-
Accessing Object Properties(訪問對象的屬性)
對象通常在接口聲明中指定屬性,而這些屬性屬于幾個類別中的一個
-
1 Attributes
這些都是一些簡單的值,比如基本類型,NSString ,NSNumber,以及其它一些不可改變類型比如NSColor都是屬于Attributes
-
2 To-one relationships
這些是具有自身屬性的可變對象, 對象的屬性可以在不改變對象本身的情況下更改,其實可以理解這個屬性為自定義對象(我自己的推測理解)
-
3 To-many relationships
這些是集合對象。你通常使用NSArray或者NSSet持有這樣一個集合,雖然自定義集合類也是可能的
把官網(wǎng)的示意圖截取下來
為了保持封裝性,一個對象通常提供的接口的屬性訪問器方法。對象的作者可以顯式地寫這些方法,也可以依賴編譯器synthesize 自動地合成它們(set和get方法),無論哪種方法,在編譯之前都得設(shè)置好屬性名或者方法名
這是直接的,但缺乏靈活性。另一方面,鍵值兼容的對象提供了一種更一般的機制來使用字符串標(biāo)識符訪問對象的屬性。
為了保持封裝性,
-
Identifying an Object’s Properties with Keys and Key Paths
鍵是標(biāo)識特定屬性的字符串
[myAccount setValue:@(100.0) forKey:@"currentBalance"];
-
Getting Attribute Value Using key
-
1. - (nullable id)valueForKey:(NSString *)key;
-
2.- (nullable id)valueForKeyPath:(NSString *)keyPath;
-
3.- (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;
-
4.- (nullable id)valueForUndefinedKey:(NSString *)key;
注意: 集合對象,比如NSArray,NSSet,NSDictionary,都不能包含為nil的值,所以如果你在設(shè)置值的時候,可以用NSNull對象,NSNull提供單實例表示對象屬性的值為nil,默認(rèn)的實現(xiàn)方法dictionaryWithValuesForKeys: 和setValuesForKeysWithDictionary:將自動轉(zhuǎn)換 NSNull (字典里的參數(shù)) and nil (儲存的屬性)
當(dāng)你使用一個鍵路徑是處理屬性時,如果鍵路徑中的最后一個鍵是一個多對關(guān)系(也就是它引用集合),那么返回的值就是這個鍵對應(yīng)所有值的一個集合,比如請求一個鍵路徑的值transactions.payee那么返回一個數(shù)組,這個數(shù)組包含transactions下payee所有的值
-
Setting Attribute Values Using Keys
-
1.- (void)setValue:(nullable id)value forKey:(NSString *)key;
-
2.- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
-
3.- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;
-
4.- (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *, id> *)keyedValues;
在默認(rèn)的實現(xiàn)中,如果你要給非對象(比如int) 的屬性設(shè)置nil,那么就會拋出一個異常,如果重寫了setNilValueForKey,就不會奔潰,也可以在這個方法里改變值
-
Using Collection Operators(集合操作)
當(dāng)你使用valueForKeyPath時,可以嵌入集合運算符在里面,集合運算符是一個小的關(guān)鍵字列表,前面是(@)
集合操作展示三種基本行為類型
-
Aggregation Operators
以某種方式合并集合的對象,然后返回一個對象,該對象通常與右鍵路徑中命名的屬性的數(shù)據(jù)類型相匹配,但是@count是一個例外,它沒有正確的關(guān)鍵路徑和總是返回一個NSNumber實例,因為它返回的是集合的個數(shù)
1.@avg
NSMutableArray * arr=[[NSMutableArray alloc ]init];
Person * personOne = [[Person alloc ] init];
personOne.age=@"13";
[arr addObject:personOne];
Person * personTwo = [[Person alloc ] init];
personTwo.age=@"14";
[arr addObject: personTwo];
Person * personThree= [[Person alloc ] init];
personThree.age=@"15";
[arr addObject: personThree];
NSLog(@"平均值=%@",arr valueForKeyPath:@"@avg.age");
//14
2.@count
返回一個NSNumber實例集合中對象的數(shù)量
NSLog(@"集合個數(shù)=%@",arr valueForKeyPath:@"@count");
//3
3.@max
在由右鍵路徑命名的集合條目中搜索并返回最大的條目。搜索進行比較使用比較的方法,如:許多基礎(chǔ)類定義的,比如NSNumber類。因此,由右鍵路徑指示的屬性必須持有對該消息有意義響應(yīng)的對象。搜索忽略空值集合條目
NSLog(@"最大值=%@",arr valueForKeyPath:@"@max.age");
//15
4.@min
NSLog(@"最小值=%@",arr valueForKeyPath:@"@min.age");
//13
5.@sum
NSLog(@"求和=%@",arr valueForKeyPath:@"@sum.age");
//42
-
Array Operators
返回與右鍵路徑指示的對象的特定集合相對應(yīng)的對象數(shù)組
1.@distinctUnionOfObjects
創(chuàng)建并返回包含與右鍵路徑指定的屬性相對應(yīng)的集合的不同對象的數(shù)組
NSLog(@"返回數(shù)組下對象的某個屬性的全部值,是個數(shù)組=%@",[arr valueForKeyPath:@"@distinctUnionOfObjects.age"]);
/*
=(
15,
13,
14
)
*/
注意:@distinctUnionOfObjects操作者會刪除重復(fù)的對象,比如如果有三個14,那么就只返回一個14
2.@unionOfObjects
跟@distinctUnionOfObjects相反,它不會刪除重復(fù)對象,如果有三個14,還是三個14全部返回
NSLog(@"返回數(shù)組下對象的某個屬性的全部值,是個數(shù)組=%@",[arr valueForKeyPath:@"@unionOfObjects.age"]);
/*
=(
13,
14,
14
)
*/
-
Nesting Operators
-
Representing Non-Object Values(表示非對象值)
簡單的總結(jié)幾句話:setValue的時候如果不是value不是對象,那么轉(zhuǎn)成對象,valueForKey的時候,因為返回的是id,如果是標(biāo)量類型比如int,float都那么返回的是NSNumber ,把NSNumber轉(zhuǎn)成int或者float,如果是結(jié)構(gòu)體類型,那么返回的就是NSValue,把NSValue轉(zhuǎn)成NSRect,NSPoint,NSSize,NSRange就行
-
Validating Properties(驗證屬性)
KVC提供了屬性值,用來驗證key對應(yīng)的Value是否可用的方法
validateValue:forKey:error: 和validateValue:forKeyPath:error方法
這個方法的默認(rèn)實現(xiàn)是去探索類里面是否有一個這樣的方法:-(BOOL)validate<Key>:error:如果有這個方法,就調(diào)用這個方法來返回,沒有的話就直接返回YES
@implementation Person
-(BOOL)validateAge:(id *)value error:(out NSError * _Nullable __autoreleasing *)outError{ //在implementation里面加這個方法,它會驗證是否設(shè)了非法的value
NSString* inValue = *value;
//country = country.capitalizedString;
if ([inValue isEqualToString:@"10"]) {
return NO;
}
return YES;
}
@end
Person * personOne =[[Person alloc] init];
personOne.age=13;
NSError* error;
id value = @"10";
NSString* key = @"age";
BOOL result = [personOne validateValue:&value forKey:key error:&error]; //如果沒有重寫-(BOOL)-validate<Key>:error:,默認(rèn)返回Yes
if (result) {
NSLog(@"鍵值匹配");
[personOne setValue:value forKey:key];
}
else{
NSLog(@"鍵值不匹配"); //不能設(shè)為日本,基他國家都行
}
NSString* age = [personOne valueForKey:@"age"];
NSLog(@"age:%@",age);
/*
2017-08-29 14:31:27.850 KVC[4580:93769] 鍵值不匹配
2017-08-29 14:31:27.850 KVC[4580:93769] age:13
*/
-
Accessor Search Patterns(搜索key的模式)
-
Setter的基本搜索方式
規(guī)則也是: 先找相關(guān)方法,再找相關(guān)變量
1.先去找相關(guān)方法 :set<Key>
2.那么去判斷類的實現(xiàn)文件中有沒有實現(xiàn):+(BOOL)accessInstanceVariablesDirectly,不實現(xiàn)默認(rèn)返回YES
3.如果返回no,則執(zhí)行setValue:(id)value forUndefinedKey:(NSString *)key(拋出一個異常,如果實現(xiàn)文件中沒有重寫該方法,則會奔潰,如果重寫了,則不會奔潰,還可以在這個方法給沒有找到的key一個替代值)
4.如果返回的yes,則找相關(guān)變量:那么按_<key>,_is<Key>,<key>,is<key>的順序搜索成員名,如果也沒找到則執(zhí)行setValue:(id)value forUndefinedKey:(NSString *)key
-
Getter的基本搜索方式
總體規(guī)則是:先找相關(guān)方法,在找相關(guān)
1.先去找相關(guān)方法,如果相關(guān)方法沒找到
2.那么去判斷類的實現(xiàn)文件中有沒有實現(xiàn):+(BOOL)accessInstanceVariablesDirectly,不實現(xiàn)默認(rèn)返回YES,
3.如果返回NO,直接執(zhí)行kvc的:setValue:(id)value forUndefinedKey:(NSString *)key方法(拋出一個異常,如果實現(xiàn)文件中沒有重寫該方法,則會奔潰,如果重寫了,則不會奔潰,還可以在這個方法給沒有找到的key一個替代值)
4.如果返回的是yes,則繼續(xù)再去找相關(guān)變量
5.如果還沒找到相關(guān),也會執(zhí)行:setValue:(id)value forUndefinedKey:(NSString *)key方法
相關(guān)方法指的是:
1.get<Key>,<key>,is<Key>,或者_<key>(官網(wǎng)上說了還可以有這個方法,但是我試了并沒有用,那么就先保險的確定前三個吧)
2.如果上面的getter沒有找到,KVC則會查找countOf<Key>,objectIn<Key>AtIndex,<Key>AtIndex格式的方法。如果countOf<Key>和另外兩個方法中的要個被找到,那么就會返回一個可以響應(yīng)NSArray所的方法的代理集合(它是NSKeyValueArray,是NSArray的子類),調(diào)用這個代理集合的方法,或者說給這個代理集合發(fā)送NSArray的方法,就會以countOf<Key>,objectIn<Key>AtIndex,<Key>AtIndex這幾個方法組合的形式調(diào)用。還有一個可選的get<Ket>:range:方法。所以你想重新定義KVC的一些功能,你可以添加這些方法,需要注意的是你的方法名要符合KVC的標(biāo)準(zhǔn)命名方法,包括方法簽名
3.還沒查到,那么查找countOf<Key>、enumeratorOf<Key>、memberOf<Key>:格式的方法。
如果這三個方法都找到,那么就返回一個可以響應(yīng)NSSet所有方法的代理集合(collection proxy object)。發(fā)送給這個代理集合(collection proxy object)的NSSet消息方法,就會以countOf<Key>、enumeratorOf<Key>、memberOf<Key>:組合的形式調(diào)用
相關(guān)變量:
那么按_<key>,_is<Key>,<key>,is<key>的順序直接搜索成員名
這里不推薦這么做,因為這樣直接訪問實例變量破壞了封裝性
-
Getter的有序集合的搜索方式(比如NSMutableArray)
mutableArrayValueForKey:搜索方式如下:
- 搜索insertObject:in<Key>AtIndex:、removeObjectFrom<Key>AtIndex:或者insert<Key>:atIndexes、remove<Key>AtIndexes:格式的方法。
如果至少一個insert方法和至少一個remove方法找到,那么同樣返回一個可以響應(yīng)NSMutableArray所有方法的代理集合。那么發(fā)送給這個代理集合的NSMutableArray消息方法,以insertObject:in<Key>AtIndex:、removeObjectFrom<Key>AtIndex:、insert<Key>:atIndexes、remove<Key>AtIndexes:組合的形式調(diào)用。還有兩個可選實現(xiàn)的接口:replaceObjectIn<Key>AtIndex:withObject:、replace<Key>AtIndexes:with<Key>:。 - 否則,搜索set<Key>:格式的方法,如果找到,那么發(fā)送給代理集合的NSMutableArray最終都會調(diào)用set<Key>:方法。
也就是說,mutableArrayValueForKey取出的代理集合修改后,用set<Key>:重新賦值回去。這樣做效率會差很多,所以推薦實現(xiàn)上面的方法。 - 否則,那么如果類方法accessInstanceVariablesDirectly返回YES,那么按_<key>,<key>的順序直接搜索成員名。如果找到,那么發(fā)送的NSMutableArray消息方法直接轉(zhuǎn)交給這個成員處理。
- 再找不到,調(diào)用setValue:forUndefinedKey:
-
Getter的搜索無序集合成員,如:NSSet
mutableSetValueForKey:搜索方式如下:
- 搜索add<Key>Object:、remove<Key>Object:或者add<Key>:、remove<Key>:格式的方法,如果至少一個insert方法和至少一個remove方法找到,那么返回一個可以響應(yīng)NSMutableSet所有方法的代理集合。那么發(fā)送給這個代理集合的NSMutableSet消息方法,以add<Key>Object:、remove<Key>Object:、add<Key>:、remove<Key>:組合的形式調(diào)用。還有兩個可選實現(xiàn)的接口:intersect<Key>、set<Key>:。
- 如果reciever是ManagedObejct,那么就不會繼續(xù)搜索了。
- 否則,搜索set<Key>:格式的方法,如果找到,那么發(fā)送給代理集合的NSMutableSet最終都會調(diào)用set<Key>:方法。也就是說,mutableSetValueForKey取出的代理集合修改后,用set<Key>:重新賦值回去。這樣做效率會差很多,所以推薦實現(xiàn)上面的方法。
- 否則,那么如果類方法accessInstanceVariablesDirectly返回YES,那么按_<key>,<key>的順序直接搜索成員名。如果找到,那么發(fā)送的NSMutableSet消息方法直接轉(zhuǎn)交給這個成員處理。
- 再找不到,調(diào)用setValue:forUndefinedKey:。
關(guān)于Getter的搜索比如mutableArrayValueForKey和
mutableSetValueForKey我?guī)缀鯖]有用過,如果有人用的很溜,請指教
-
總結(jié)
相比直接訪問KVC的效率會稍低一點,所以只有當(dāng)你非常需要它提供的可擴展性時才使用它。