概述
KVC 全稱 key valued coding 鍵值編碼。
不得不承認(rèn)KVC在開發(fā)過程中是神器一般的存在芳肌。如果正確靈活使用kvc候衍,會使得整個(gè)開發(fā)過程輕松很多。簡單而強(qiáng)大芽唇。
反射機(jī)制是在運(yùn)行狀態(tài)中,對于任意一個(gè)類取劫,都能夠知道這個(gè)類的所有屬性和方法匆笤;對于任意一個(gè)對象,都能夠調(diào)用它的任意一個(gè)方法和屬性.JAVA,C#都有這個(gè)機(jī)制谱邪。ObjC也有炮捧,所以你根部不必進(jìn)行任何操作就可以進(jìn)行屬性的動(dòng)態(tài)讀寫,就是KVC惦银。
KVC的操作方法由NSKeyValueCoding提供咆课,而他是NSObject的類別,也就是說ObjC中幾乎所有的對象都支持KVC操作扯俱。它提供一種機(jī)制來間接訪問對象的屬性书蚪。直接訪問對象是通過調(diào)用訪問器的方法實(shí)現(xiàn),而KVC不需要調(diào)用訪問器的設(shè)置和獲取方法迅栅。
KVC的主要方法和用途
- (nullable id)valueForKey:(NSString *)key; //直接通過Key來取值
- (void)setValue:(nullable id)value forKey:(NSString *)key; //通過Key來設(shè)值
- (nullable id)valueForKeyPath:(NSString *)keyPath; //通過KeyPath來取值
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath; //通過KeyPath來設(shè)值
************************************************************************
當(dāng)然NSKeyValueCoding類別中還有其他的一些方法殊校,下面列舉一些
+ (BOOL)accessInstanceVariablesDirectly;
//默認(rèn)返回YES,表示如果沒有找到Set<Key>方法的話读存,會按照_key为流,_iskey,key让簿,iskey的順序搜索成員敬察,設(shè)置成NO就不這樣搜索
- (BOOL)validateValue:(inout id __nullable * __nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;
//KVC提供屬性值正確性?驗(yàn)證的API,它可以用來檢查set的值是否正確尔当、為不正確的值做一個(gè)替換值或者拒絕設(shè)置新值并返回錯(cuò)誤原因莲祸。
- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;
//這是集合操作的API,里面還有一系列這樣的API居凶,如果屬性是一個(gè)NSMutableArray虫给,那么可以用這個(gè)方法來返回藤抡。
- (nullable id)valueForUndefinedKey:(NSString *)key;
//如果Key不存在侠碧,且沒有KVC無法搜索到任何和Key有關(guān)的字段或者屬性,則會調(diào)用這個(gè)方法缠黍,默認(rèn)是拋出異常弄兜。
- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;
//和上一個(gè)方法一樣,但這個(gè)方法是設(shè)值。
- (void)setNilValueForKey:(NSString *)key;
//如果你在SetValue方法時(shí)面給Value傳nil替饿,則會調(diào)用這個(gè)方法
- (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;
//輸入一組key,返回該組key對應(yīng)的Value语泽,再轉(zhuǎn)成字典返回,用于將Model轉(zhuǎn)到字典视卢。
setValue:forKey:方法:給模型的屬性賦值
賦值原理:(以 setIcon為例:)
(1)去模型中查找有沒有setIcon方法,就直接調(diào)用這個(gè)set方法,給模型這個(gè)屬性賦值[self setIcon:dict[@"icon"]];
(2)如果找不到set方法踱卵,接著就會去尋找有沒有icon屬性,如果有,就直接訪問模型中icon = dict[@"icon"];
(3)如果找不到icon屬性,接著又會去尋找_icon屬性,如果有,直接_icon = dict[@"icon"];
(4)如果都找不到就會報(bào)錯(cuò)
[<Flag 0x7fb74bc7a2c0> setValue:forUndefinedKey:]
- 直接賦值
- 支持鍵值路徑
- 支持操作符
- 字典轉(zhuǎn)模型
- 修改UI私有屬性
直接賦值
對于屬性值我們可以通過setter 和getter方法据过,或讀取或?qū)懭霐?shù)值惋砂。
當(dāng)然我們也可以用KVC 的方式進(jìn)行讀寫數(shù)據(jù)。
舉個(gè)例子:
@interface Person : NSObject
@property(nonatomic,copy,readonly)NSString* name;
@property(nonatomic,assign)NSNumber *age;
@end
Person *person=[[Person alloc] init];
[person setValue:@"25" forKey:@"age"];
[person setValue:@"皮拉夫大王" forKey:@"name"];
NSLog(@"person 的名字是%@",person.name);
NSLog(@"person 的年領(lǐng)是%@",[person valueForKey:@"age"]);
從上面的例子中我們可以發(fā)現(xiàn):
- 只讀的屬性怎么可以賦值绳锅?
- 還有age屬性明明是NSNumber類型的西饵,怎么可以把字符串賦給它?
(1)KVC 不但能夠賦值鳞芙,而且還能破壞只讀的特性眷柔。
(2)更重要的是KVC 有自動(dòng)裝箱(自動(dòng)類型轉(zhuǎn)換)的功能,我們不需要去轉(zhuǎn)換類型了原朝。由于開發(fā)過程中數(shù)據(jù)領(lǐng)域是字符串的天下驯嘱,所以這個(gè)自動(dòng)裝箱的功能的確是極好的。
(3)KVC可以訪問成員變量喳坠,無論是否提供getter/setter方法宙拉,無論可見性是怎樣,是否有readonly修飾丙笋。
支持鍵值路徑
什么叫支持鍵值路徑谢澈?說白了就是支持多層級屬性直接賦值。假如現(xiàn)在有一個(gè)書籍類御板,類中包含了書籍的名稱name锥忿。書籍可以被Person所擁有(就是可以作為person的屬性)
#import <Foundation/Foundation.h>
@interface Book : NSObject
@property(nonatomic,copy)NSString* name;
@end
那么我們就可以這樣來用
Person *person=[[Person alloc] init];
Book *myBook=[[Book alloc] init];
person.book=myBook;
[person setValue:@"程序員攤煎餅指南" forKeyPath:@"book.name"];
NSLog(@"%@",[person valueForKeyPath:@"book.name"]);
需要說明的是:在不必要的情況下使用keyPath會浪費(fèi)性能。
支持操作符
格式為:[p valueForKeyPath:@"Left keypath部分.@Collectionoperator部分.Right keypath部分”];
Left keypath部分:需要操作對象路徑怠肋。
Collectionoperator部分:通過@符號確定使用的集合操作敬鬓。
Right keypath部分:需要進(jìn)行集合操作的屬性。
(1)簡單集合操作符
@count: 返回一個(gè)值為集合中對象總數(shù)的NSNumber對象笙各。
@sum: 首先把集合中的每個(gè)對象都轉(zhuǎn)換為double類型钉答,然后計(jì)算其總,最后返回一個(gè)值為這個(gè)總和的NSNumber對象杈抢。
@avg: 把集合中的每個(gè)對象都轉(zhuǎn)換為double類型数尿,返回一個(gè)值為平均值的NSNumber對象。
@max: 使用compare:方法來確定最大值惶楼。所以為了讓其正常工作右蹦,集合中所有的對象都必須支持和另一個(gè)對象的比較诊杆。
@min: 和@max一樣,但是返回的是集合中的最小值何陆。
[products valueForKeyPath:@"@count"];
[products valueForKeyPath:@"@sum.price"];
[products valueForKeyPath:@"@avg.price"];
[products valueForKeyPath:@"@max.price"];
[products valueForKeyPath:@"@min.launchedOn"];
如果操作對象(集合/數(shù)組)內(nèi)是NSNumber晨汹,可以這樣寫
[products valueForKeyPath:@"@sum.self"];
1.使用類的方法做操作符
NSArray *array = @[@"name", @"w", @"aa", @"jimsa"];
NSLog(@"%@", [array valueForKeyPath:@"uppercaseString"]);
輸出:
( NAME, W, AA, JIMSA)
相當(dāng)于數(shù)組中的每個(gè)成員執(zhí)行了uppercaseString方法,然后把返回的對象組成一個(gè)新數(shù)組返回贷盲。既然可以用uppercaseString方法淘这,那么NSString的其他方法也是可以的.
2.剔除重復(fù)數(shù)據(jù)
NSArray *array = @[@"name", @"w", @"aa", @"jimsa", @"aa"]; NSLog(@"%@",
[array valueForKeyPath:@"@distinctUnionOfObjects.self"]);
打印:
( name, w, jimsa, aa )
3.對NSDictionary數(shù)組快速找出相應(yīng)key對的值
NSArray *array = @[
@{@"name1" : @"cookeee",@"code" : @1},
@{@"name": @"jim",@"code" : @2},
@{@"name": @"jim",@"code" : @1},
@{@"name": @"jbos",@"code" : @1}];
NSLog(@"%@", [array valueForKeyPath:@"name"]);
直接得到字典中name key對應(yīng)的值組成的數(shù)組巩剖,顯然比循環(huán)取值再加入到新數(shù)組中方便快捷慨灭,
由于第一個(gè)元素沒有name這個(gè)Key ,所以里面為<null>)
( "<null>", jim, jim, jbos )
(2)對象操作符
@unionOfObjects:返回操作對象內(nèi)部的所有對象球及,返回值為數(shù)組
@distinctUnionOfObjects:返回操作對象內(nèi)部的不同對象氧骤,返回值為數(shù)組
(3)數(shù)組和集合操作符
@unionOfArrays:返回操作對象(且操作對象內(nèi)對象必須是數(shù)組/集合)中數(shù)組/集合的所有對象,返回值為數(shù)組
@distinctUnionOfArrays:返回操作對象(且操作對象內(nèi)對象必須是數(shù)組/集合)中數(shù)組/集合的不同對象吃引,返回值為數(shù)組
@distinctUnionOfSets:返回操作對象(且操作對象內(nèi)對象必須是數(shù)組/集合)中數(shù)組/集合的所有對象筹陵,返回值為集合
提示:集合無重復(fù)元素
(4)自定義操作符
以NSArray
為例,runtime跑一下
#import <objc/runtime.h>
unsigned int outconunt = 0;
Method *meths =class_copyMethodList([NSArray class], &outconunt);
for (int i = 0; i<outconunt; i++) {
Method meth = meths[i];
SEL metSel = method_getName(meth);
NSLog(@"L : %@",NSStringFromSelector(metSel));
}
可以看到一大堆的方法镊尺,由于太多了朦佩,無法截圖完整的,看上圖紅框中的代碼是不是很眼熟庐氮。
猜想:實(shí)現(xiàn)_<key>ForKeyPath:
即可自定義Collection Operators
嘗試定義一個(gè)名為@jack
的Collection Operators
可見语稠,只要寫好實(shí)現(xiàn),完全可以自定義一些比較有用的Collection Operators
字典轉(zhuǎn)模型
下面是常見的使用方法弄砍,目前有很多KVC 和 Runtime一起使用達(dá)到Json數(shù)據(jù)自動(dòng)轉(zhuǎn)模型的方法仙畦,本文暫時(shí)不做介紹。
@implementation Model
-(instancetype)initWithDict:(NSDictionary *)dict
{
if (self=[super init])
{
// 字典轉(zhuǎn)模型的常用語句
[self setValuesForKeysWithDictionary:dict];
}
return self;
}
-(void)setValue:(id)value forUndefinedKey:(NSString *)key
{
if ([key isEqualToString:@"id"])
{
self.whoCare=value;
}
}
@end
修改UI私有屬性
(1)如何實(shí)現(xiàn)這樣的效果音婶?
系統(tǒng)默認(rèn)的是這樣的:
看了系統(tǒng)自帶的API慨畸,無法解決這個(gè)問題,現(xiàn)在有兩個(gè)路:
- 自定義PageControl
- 通過runtime遍歷出UIPageControl所有屬性(包括私有成員屬性)利用KVC可強(qiáng)制修改系統(tǒng)的PageControl衣式,達(dá)到想要的效果寸士。充滿了黑科技之感
u_int count;
Ivar *properties =class_copyIvarList([UIPageControl class], &count);
for (int i = 0; i<count; i++)
{
const char* propertyName =ivar_getName(properties[i]);
const char* propertyType = ivar_getTypeEncoding(properties[i]);
NSLog(@"屬性:%@ = %@",[NSString stringWithUTF8String: propertyName],[NSString stringWithUTF8String: propertyType]);
}
結(jié)果非常滿意,果然找到我想要的圖片設(shè)置屬性碴卧。
然后通過KVC設(shè)置自定義圖片弱卡,實(shí)現(xiàn)了效果,代碼如下:
UIPageControl *pageControl = [[UIPageControl alloc] init];
[pageControl setValue:[UIImage imageNamed:@"home_slipt_nor"] forKeyPath:@"_pageImage"];
[pageControl setValue:[UIImage imageNamed:@"home_slipt_pre"] forKeyPath:@"_currentPageImage"];
(2)還有很多其他的修改UI控件私有屬性的常見操作:
比如修改UISearchBar的輸入框顯示效果住册。
UITextField * searchField = [searchBar valueForKey:@"searchField"];
[searchField setValue:GrayTextColor forKeyPath:@"placeholderLabel.textColor"];
[searchField setValue:[UIFont boldSystemFontOfSize:10] forKeyPath:@"_placeholderLabel.font"];
UITextField *searchField = [[mySearchBar subviews] lastObject];
[searchField setReturnKeyType:UIReturnKeyDone];
參考文章:
iOS開發(fā)技巧系列---詳解KVC
KVC進(jìn)階(三)
iOS底層-KVC使用實(shí)踐以及實(shí)現(xiàn)原理
iOS開發(fā)技巧系列---詳解KVC(我告訴你KVC的一切)