一所计、什么是KVC省古?
KVC的全稱是Key-Value Coding
奇唤,翻譯成中文是 鍵值編碼
蓄氧,鍵值編碼是由NSKeyValueCoding非正式協(xié)議
啟用的一種機(jī)制疟位,對象采用該協(xié)議來間接訪問其屬性。實際上版扩,KVC是對NSObject的協(xié)議擴(kuò)展废离,當(dāng)然也支持NSArray、NSDictionary礁芦、NSMutableDictionary蜻韭、NSOrderedSet、NSSet柿扣。
1. 最常用的四個方法
//直接通過Key來取值
- (nullable id)valueForKey:(NSString *)key;
//通過Key來設(shè)值
- (void)setValue:(nullable id)value forKey:(NSString *)key;
//通過KeyPath來取值
- (nullable id)valueForKeyPath:(NSString *)keyPath;
//通過KeyPath來設(shè)值
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
2. 最簡單的使用例子
現(xiàn)在有一個銀行賬戶類BankAccount
肖方,銀行賬戶有余額currentBalance
屬性,還有所屬的用戶信息Person
類未状。
BankAccount類
@class Person;
@class Transaction;
@interface BankAccount : NSObject
@property (nonatomic, strong) NSNumber *currentBalance; //當(dāng)前余額
@property (nonatomic, strong) Person *owner; //賬戶
@end
Person類
@interface Person : NSObject
@property (nonatomic, copy) NSString *name; // 名字
@property (nonatomic, copy) NSString *age; // 年齡
@end
我們?nèi)粘i_發(fā)中使用最多的賦值就是下面這種
BankAccount *bankAcount = [[BankAccount alloc]init];
Person *person = [Person new];
person.name = @"根哥";
person.age = @"20";
bankAcount.currentBalance = @2000;
bankAcount.owner = person;
接下來就是KVC賦值了
BankAccount *bankAcount = [[BankAccount alloc]init];
Person *person = [Person new];
bankAcount.owner = person; //此處必須先初始化一個實例對象俯画,才可進(jìn)行賦值。
[bankAcount setValue:@(200) forKey:@"currentBalance"];
[bankAcount setValue:@"28" forKeyPath:@"owner.age"];
這個簡單的例子只是為了讓你對KVC稍微有點了解娩践。
二活翩、KVC底層原理
那么KVC是怎么通過字符串Key就可以給對象賦值的了,這就要說到KVC的賦值邏輯了翻伺。
- 當(dāng)調(diào)用
setValue:forKey:
設(shè)置屬性value時材泄,其底層的執(zhí)行流程為
- 首先去對象里面找是否有setter方法,如果有則給成員變量key賦值吨岭。
- 如果沒有實現(xiàn)setter方法拉宗,則會看對象的
+ (BOOL)accessInstanceVariablesDirectly
(它表示是否允許讀取實例變量的值)是否返回YES,如果返回YES辣辫,則繼續(xù)步驟3旦事,否則會調(diào)用setValue:forUndefinedKey:
方法,并拋出異常急灭。 - 滿足2的條件下 姐浮,會查找一個命名規(guī)則為_<key>、_is<Key>葬馋、<key>卖鲤、is<Key>的實例變量。根據(jù)這個順序畴嘶,如果發(fā)現(xiàn)則將value賦值給實例變量蛋逾。
- get的搜索規(guī)則
get的搜索規(guī)則相對于set就有點復(fù)雜了,下面只做了解就可以了
1.通過getter方法搜索實例,例如get<Key>, <key>, is<Key>, <key>的拼接方案窗悯。按照這個順序区匣,如果發(fā)現(xiàn)符合的方法,就調(diào)用對應(yīng)的方法并拿著結(jié)果跳轉(zhuǎn)到第五步蒋院。否則亏钩,就繼續(xù)到下一步莲绰。
2.如果沒有找到簡單的getter方法,則搜索其匹配模式的方法countOf<Key>铸屉、objectIn<Key>AtIndex:钉蒲、<key>AtIndexes:。如果找到其中的第一個和其他兩個中的一個彻坛,則創(chuàng)建一個集合代理對象NSKeyValueArray顷啼,該對象響應(yīng)所有NSArray的方法并返回該對象。否則昌屉,繼續(xù)到第三步钙蒙。代理對象隨后將NSArray接收到的countOf<Key>、objectIn<Key>AtIndex:间驮、<key>AtIndexes:的消息給符合KVC規(guī)則的調(diào)用方躬厌。當(dāng)代理對象和KVC調(diào)用方通過上面方法一起工作時,就會允許其行為類似于NSArray一樣竞帽。
3.如果沒有找到NSArray簡單存取方法扛施,或者NSArray存取方法組。則查找有沒有countOf<Key>屹篓、enumeratorOf<Key>疙渣、memberOf<Key>:命名的方法。如果找到三個方法堆巧,則創(chuàng)建一個集合代理對象妄荔,該對象響應(yīng)所有NSSet方法并返回。否則谍肤,繼續(xù)執(zhí)行第四步啦租。此代理對象隨后轉(zhuǎn)換countOf<Key>、enumeratorOf<Key>荒揣、memberOf<Key>:方法調(diào)用到創(chuàng)建它的對象上篷角。實際上,這個代理對象和NSSet一起工作系任,使得其表象上看起來是NSSet恳蹲。
4.如果沒有發(fā)現(xiàn)簡單getter方法,或集合存取方法組赋除,以及接收類方法accessInstanceVariablesDirectly是返回YES的阱缓。搜索一個名為<key>非凌、_is<Key>举农、<key>、is<Key>的實例敞嗡,根據(jù)他們的順序颁糟。如果發(fā)現(xiàn)對應(yīng)的實例航背,則立刻獲得實例可用的值并跳轉(zhuǎn)到第五步,否則棱貌,跳轉(zhuǎn)到第六步玖媚。
5.如果取回的是一個對象指針,則直接返回這個結(jié)果婚脱。如果取回的是一個基礎(chǔ)數(shù)據(jù)類型今魔,但是這個基礎(chǔ)數(shù)據(jù)類型是被NSNumber支持的,則存儲為NSNumber并返回障贸。如果取回的是一個不支持NSNumber的基礎(chǔ)數(shù)據(jù)類型错森,則通過NSValue進(jìn)行存儲并返回。
6.如果所有情況都失敗篮洁,則調(diào)用valueForUndefinedKey:方法并拋出異常涩维,這是默認(rèn)行為。但是子類可以重寫此方法袁波。
三瓦阐、應(yīng)用場景
1,用KVC中的函數(shù)來操作集合(集合主要指NSArray和NSSet,不包括NSDictionary)
上面的圖是集合運(yùn)算符的格式篷牌,主要是對象調(diào)用valueForKeyPath:
方法進(jìn)行操作睡蟋。運(yùn)算符有三種:
1)簡單集合運(yùn)算符共有@avg, @count 娃磺, @max 薄湿, @min ,@sum5種
Transaction *transaction = [[Transaction alloc]init];
transaction.name = @"3";
transaction.money = @23;
Transaction *transaction1 = [[Transaction alloc]init];
transaction1.name = @"3";
transaction1.money = @1000;
NSArray *tempArray = [NSArray arrayWithObjects:transaction,transaction1, nil];
NSNumber *maxValue = [tempArray valueForKeyPath:@"@max.money"];
就是遍歷數(shù)組的每一個對象偷卧,然后對應(yīng)屬性的最大值豺瘤。
2)對象運(yùn)算@distinctUnionOfObjects
返回去重后的數(shù)組,@unionOfObjects
返回數(shù)組
Transaction *transaction = [[Transaction alloc]init];
transaction.name = @"3";
transaction.money = @23;
Transaction *transaction1 = [[Transaction alloc]init];
transaction1.name = @"3";
transaction1.money = @1000;
NSArray *tempArray = [NSArray arrayWithObjects:transaction,transaction1, nil];
NSNumber *avg = [tempArray valueForKeyPath:@"@max.money"];
//返回對象中name的數(shù)組 下面兩種方式一樣
NSArray *tempArray1 = [tempArray valueForKeyPath:@"@unionOfObjects.name"]; //[@"3", @"3"]
NSArray *resultArray = [tempArray valueForKeyPath:@"name"]; //[@"3", @"3"]
// NSSet *set = [NSSet setWithArray:tempArray1]; //去重
//@distinctUnionOfObjects將集合對象中听诸,所有Transaction對象放在一個數(shù)組中坐求,并將數(shù)組進(jìn)行去重后返回。 比unionOfObjects多一步晌梨,會去重
NSArray *tempArray2 = [tempArray valueForKeyPath:@"@distinctUnionOfObjects.name"]; //[@"3"]
3)Array和Set嵌套操作符@distinctUnionOfArrays
,@distinctUnionOfSets
,@unionOfArrays
就是數(shù)組或集合的雙重嵌套桥嗤。
NSArray *dArray = @[tempArray,tempArray];
NSArray *resultArray2 = [dArray valueForKeyPath:@"@distinctUnionOfArrays.name"]; //[@"3"]
2. 使用KVC實現(xiàn)高階消息傳遞(就是對集合中的所有對象都執(zhí)行某個方法,跟上面的函數(shù)運(yùn)算類似)
NSArray* stringArray = @[@"china",@"japan",@"korea"];
NSArray* capArray = [stringArray valueForKey:@"capitalizedString"]; //執(zhí)行首字符轉(zhuǎn)大寫的操作
for (NSString* string in capArray) {
NSLog(@"首字母大寫:%@",string); //China Japan Korea
}
NSArray* arrCapStrLength = [stringArray valueForKeyPath:@"capitalizedString.length"];
for (NSNumber* length in arrCapStrLength) {
NSLog(@"%ld",(long)length.integerValue); 5 5 5
}
此處有個非常實用的方法,就是將字符串的首字符轉(zhuǎn)為大寫其余小寫#capitalizedString#
NSString *name = @"name";
name = name.capitalizedString; //Name
NSString *testString = @"helloWord";
testString = testString.capitalizedString;//Helloword
3, 多值操作(model和字典互轉(zhuǎn))
- 假設(shè)dict字典中有name仔蝌,icon 的Key泛领,XGYModel模型類中必須要有同名的name,icon屬性與之相對應(yīng)敛惊。
- 我們使用
[XGYModel setValuesForKeysWithDictionary:dict]
;進(jìn)行字典轉(zhuǎn)模型渊鞋。
字典轉(zhuǎn)模型的原理:
// enumerateKeysAndObjectsUsingBlock:遍歷字典中的所有keys和valus
[dict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
// 利用KVC給模型中屬性賦值,,
// key:用來給哪個屬性
// Value:給模型的值
[XGYModel setValue:obj forKey:key];
}];
4. 修改內(nèi)部控件的私有屬性
比如自定義UITabbar,UIPageControl ....
//后續(xù)有待補(bǔ)充...