key-value coding

  • 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é)議)


0410AF7F-1341-400A-ADD9-9143B03419E0.png

所以其實簡單的來說只要滿足第一個條件它就有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)的示意圖截取下來

1417FE9F-022F-4E3B-A2C5-F22C9233F679.png

為了保持封裝性,一個對象通常提供的接口的屬性訪問器方法。對象的作者可以顯式地寫這些方法,也可以依賴編譯器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)鍵字列表,前面是(@)

A6B7444C-84C7-4523-B054-08888C4846B6.png

集合操作展示三種基本行為類型

  • 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:搜索方式如下:

  1. 搜索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>:。
  2. 否則,搜索set<Key>:格式的方法,如果找到,那么發(fā)送給代理集合的NSMutableArray最終都會調(diào)用set<Key>:方法。
    也就是說,mutableArrayValueForKey取出的代理集合修改后,用set<Key>:重新賦值回去。這樣做效率會差很多,所以推薦實現(xiàn)上面的方法。
  3. 否則,那么如果類方法accessInstanceVariablesDirectly返回YES,那么按_<key>,<key>的順序直接搜索成員名。如果找到,那么發(fā)送的NSMutableArray消息方法直接轉(zhuǎn)交給這個成員處理。
  4. 再找不到,調(diào)用setValue:forUndefinedKey:
  • Getter的搜索無序集合成員,如:NSSet

mutableSetValueForKey:搜索方式如下:

  1. 搜索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>:。
  2. 如果reciever是ManagedObejct,那么就不會繼續(xù)搜索了。
  3. 否則,搜索set<Key>:格式的方法,如果找到,那么發(fā)送給代理集合的NSMutableSet最終都會調(diào)用set<Key>:方法。也就是說,mutableSetValueForKey取出的代理集合修改后,用set<Key>:重新賦值回去。這樣做效率會差很多,所以推薦實現(xiàn)上面的方法。
  4. 否則,那么如果類方法accessInstanceVariablesDirectly返回YES,那么按_<key>,<key>的順序直接搜索成員名。如果找到,那么發(fā)送的NSMutableSet消息方法直接轉(zhuǎn)交給這個成員處理。
  5. 再找不到,調(diào)用setValue:forUndefinedKey:。

關(guān)于Getter的搜索比如mutableArrayValueForKey和
mutableSetValueForKey我?guī)缀鯖]有用過,如果有人用的很溜,請指教

  • 總結(jié)

相比直接訪問KVC的效率會稍低一點,所以只有當(dāng)你非常需要它提供的可擴展性時才使用它。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容