一猫态、KVC簡介
KVC提供了一套不通過訪問器方法或者屬性變量,通過Key或者KeyPath直接訪問對象屬性的機(jī)制疟游。KVC是以下技術(shù)的實現(xiàn)基礎(chǔ)KVO审洞、Core Data莱睁、Cocoa bindings、AppleScript芒澜。KVC性能略遜于訪問器和實例變量仰剿,但是靈活性高,很多時候可以簡化代碼痴晦。使用KVC需要實現(xiàn)其存取方法酥馍,相關(guān)的方法都在Objective-C的NSKeyValueCoding協(xié)議中聲明,超級父類NSObject
默認(rèn)遵守該協(xié)議阅酪。KVC支持對象屬性(如NSSting)同時也指出非對象屬性(基本數(shù)據(jù)類型和結(jié)構(gòu)體旨袒,提供自動轉(zhuǎn)換數(shù)據(jù)類型)。
二术辐、KVC基本原理
首先區(qū)分兩個基本概念
名稱 | 內(nèi)容 |
---|---|
Key | Key是標(biāo)識對象具體屬性的字符串砚尽,相當(dāng)于對象的訪問器名稱或者變量名稱,不能包含空格辉词。 |
KeyPath | KeyPath是指定對象一系列屬性必孤,且用.分割每個屬性的字符串。字符串序列中的每個key標(biāo)識前面對象的屬性。比如說people.address.street能夠獲取people的address屬性敷搪,然后獲取到address的street屬性兴想。 |
然后說明等的執(zhí)行過程,KVC的方法從功能上分存赡勘、取兩種方法setValue:forKey:
和valueForKey:
嫂便,以這兩個方法為代表描述執(zhí)行過程。
首先setValue:forKey:
的執(zhí)行過程
1闸与、首先對象方法列表中匹配方法-set<Key>:
2毙替、如果第1步失敗而且 accessInstanceVariablesDirectly
返回YES,按照以下順序匹配實例變量_<key>, _is<Key>, <key>, or is<Key>
3、如果前2步任一成功践樱,則進(jìn)行賦值厂画。必要的話進(jìn)行數(shù)據(jù)類型轉(zhuǎn)換。
4拷邢、如果前3步進(jìn)行失敗則調(diào)用 setValue:forUndefinedKey:
拋出NSUndefinedKeyException
異常袱院。
注:方法setValue:forKey:根據(jù)指定路徑獲取屬性值,KeyPath中每一個key都進(jìn)行以上步驟瞭稼;也就是說任何一個key出錯忽洛,都會拋出異常。
代碼2.1
@interface ViewController ()
{
NSString *_name;
NSString *_isName;
NSString *name;
NSString *isName;
}
@property (nonatomic,copy)NSString *name;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self setValue:@"zwq" forKey:@"name"];
NSLog(@"_name:%@",_name);
NSLog(@"_isName:%@",_isName);
NSLog(@"name:%@",name);
NSLog(@"isName:%@",isName);
}
//可以通過以上代碼(注釋部分代碼)來驗證上述過程弛姜。
然后是valueForKey:
執(zhí)行過程
1脐瑰、首先按照此順序匹配方法 get<Key>, <key>, or is<Key>,
如果匹配成功調(diào)用方法妖枚,返回結(jié)果廷臼。必要的話進(jìn)行數(shù)據(jù)類型轉(zhuǎn)換。
2绝页、如果1步進(jìn)行失敗荠商,則匹配以下方法 countOf<Key>、 objectIn<Key>AtIndex: 续誉、 <key>AtIndexes:
若找打其中一個莱没,則返回容器類對象。該對象調(diào)用以上方法酷鸦,會調(diào)用valueForKey:方法饰躲。(NSArray類的方法)
3、如果前2步失敗臼隔,則匹配以下方法countOf<Key>, enumeratorOf<Key>, and memberOf<Key>:
若找打其中一個嘹裂,則返回容器類對象。該對象調(diào)用以上方法摔握,會調(diào)用valueForKey:方法寄狼。
(NSSet類的方法)
4、如果前3步失敗氨淌,而且 accessInstanceVariablesDirectly
返回YES泊愧,按照以下順序匹配實例變量_<key>, _is<Key>, <key>, or is<Key>
伊磺。如果實例變量找到了,則進(jìn)行復(fù)制删咱。必要的話進(jìn)行數(shù)據(jù)類型轉(zhuǎn)換屑埋。
5、如果前4步進(jìn)行失敗則調(diào)用 valueForUndefinedKey:
拋出NSUndefinedKeyException
異常腋腮。
注:
1雀彼、方法valueForKeyPath:根據(jù)指定路徑獲取屬性值,KeyPath中每一個key都進(jìn)行以上步驟即寡;也就是說任何一個key出錯徊哑,都會拋出異常。
2聪富、如果KeyPath序列中包含了一個key是一對多的關(guān)系莺丑,而且這個key不是最后一個,那么將返回所有對象的屬性值墩蔓。例如accounts.transactions.payee將返回所有account的所有transaction的所有payee值梢莽。
//VC有一個數(shù)組屬性
@property (nonatomic,assign)NSArray *array;
- (void)viewDidLoad {
[super viewDidLoad];
//Data有一個name屬性
Data *data1 = [[Data alloc] init];
Data *data2 = [[Data alloc] init];
Data *data3 = [[Data alloc] init];
data1.name=@"data1";
data2.name=@"data2";
data3.name=@"data3";
//self.array.name
NSArray *arr = [NSArray arrayWithObjects:data1,data2,data3, nil];
[self setValue:arr forKey:@"array"];
NSLog(@"array:%@",[self valueForKeyPath:@"array.name"]);
}
輸出結(jié)果
2016-09-01 17:05:57.235 KVC[3467:249694] array:(
data1,
data2,
data3
)
可以仿照代碼2.1進(jìn)行代碼驗證。由上邊底層執(zhí)行過程不難看出:KVC性能略遜于訪問器和實例變量奸披,但是靈活性高昏名,視情況選擇。
說明:
1阵面、必要的話進(jìn)行數(shù)據(jù)類型轉(zhuǎn)換:KVC對應(yīng)非對象類型進(jìn)行自動數(shù)據(jù)類型轉(zhuǎn)換轻局,下文做詳細(xì)說明。
2样刷、方法accessInstanceVariablesDirectly的說明:默認(rèn)返回YES仑扑,表示對象的實例變量可以直接訪問。
3置鼻、關(guān)于NSUndefinedKeyException異常的處理镇饮,下文做詳細(xì)說明
三、異常處理
1箕母、方法valueForKey:
尋找不到指定Key或者KeyPath匹配的方法或變量名稱會自動調(diào)用valueForUndefinedKey:
拋出NSUndefinedKeyException
異常
2储藐、方法setValue:forKey:
尋找不到指定Key或者KeyPath匹配的方法或變量名稱會自動調(diào)用setValue:forUndefinedKey:
拋出NSUndefinedKeyException
異常
//NSUndefinedKeyException如下所示
*** Terminating app due to uncaught exception 'NSUnknownKeyException',
reason: '[<ViewController 0x7fd60b728690> setValue:forUndefinedKey:]:
this class is not key value coding-compliant for the key age.'
處理方法為重寫此二者方法
- (nullable id)valueForUndefinedKey:(NSString *)key;
- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;
方法體可為空也可自定義處理
//空處理
- (nullable id)valueForUndefinedKey:(NSString *)key
{
return nil;
}
- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key
{
}
//自定義處理
- (nullable id)valueForUndefinedKey:(NSString *)key
{
if ([key isEqualToString:@"key"]) {
//返回內(nèi)容自定義
return nil;
}
return nil;
}
- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key
{
if ([key isEqualToString:@"key"])
{
//返回內(nèi)容自定義
}
}
四、非對象類型的處理
KVC對于基本數(shù)據(jù)類型和結(jié)構(gòu)體在底層支持自動數(shù)據(jù)類型轉(zhuǎn)換嘶是。根據(jù)相對的存取方法或者實例變量判端實際需要的值類型钙勃,選擇NSNumber 或 NSValue 進(jìn)行自動轉(zhuǎn)換。
1俊啼、NSNumber對應(yīng)的基本數(shù)據(jù)類型
例如
@property (nonatomic,assign)BOOL fail;
- (void)viewDidLoad {
[super viewDidLoad];
NSNumber *num = [NSNumber numberWithBool:0];
NSLog(@"class:%@",[num class]);
[self setValue:@"0" forKey:@"fail"];
NSLog(@"fali:%d--class:%@",self.fail,[[self valueForKey:@"fail"] class]);
}
輸出結(jié)果:
2016-09-01 14:27:33.401 KVC[2672:154097] class:__NSCFBoolean
2016-09-01 14:27:33.401 KVC[2672:154097] fali:0--class:__NSCFBoolean
2肺缕、NSValue對應(yīng)的結(jié)構(gòu)體類型
例如
@property (nonatomic,assign)CGPoint point;
NSValue *value = [NSValue valueWithCGPoint:CGPointMake(1, 1)];
NSLog(@"class:%@",[value class]);
[self setValue:value forKey:@"point"];
NSLog(@"fali:%@--class:%@",NSStringFromCGPoint(self.point) ,[[self valueForKey:@"point"] class]);
輸出結(jié)果:
2016-09-01 14:40:23.599 KVC[2751:163036] class:NSConcreteValue
2016-09-01 14:40:23.599 KVC[2751:163036] fali:{1, 1}--class:NSConcreteValue
3、注意事項
對非對象類型的屬性設(shè)置nil空值,底層調(diào)用setNilValueForKey:
同木,然后拋出NSInvalidArgumentException
異常
例如
[self setValue:nil forKey:@"fail"];
//或
[self setValue:nil forKey:@"point"];
異常:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException',
reason: '[<ViewController 0x7fd769484b90> setNilValueForKey]:
could not set nil as the value for the key fail.'
解決方法是重寫該方法setNilValueForKey:
浮梢,方法可空也可自定義處理,例如
-(void)setNilValueForKey:(NSString *)key
{
//自定義內(nèi)容
if ([key isEqualToString:@"fail"])
{
[self setValue:[NSNumber numberWithBool:0] forKey:@"fail"];
}
if ([key isEqualToString:@"point"])
{
[self setValue:[NSValue valueWithCGPoint:CGPointZero] forKey:@"point"];
}
}
五彤路、Key-Value Validation
這個標(biāo)題就不翻譯了秕硝,英文更容易理解。
- validateValue:forKey:error:
- validateValue:forKeyPath:error:
KVC提供一套API使得屬性值生效洲尊。使得對象有機(jī)會接受值远豺、提供默認(rèn)值、拒絕新值坞嘀、拋出錯誤原因躯护。KVC不會自動調(diào)用,需要手動調(diào)用丽涩。默認(rèn)實現(xiàn)過程:
1棺滞、調(diào)用validateValue:forKey:error:
2、在對象的方法列表中匹配validate<Key>:error:
3矢渊、如果找到則執(zhí)行并返回結(jié)果
4继准、如果未找到則返回YES,并賦值
注意:set方法中禁止調(diào)用
@property (nonatomic,assign)NSInteger age;
-(BOOL)validateAge:(id *)ioValue error:(NSError **)outError
{
if (*ioValue == nil)
{
// 年齡大于0歲
[self setValue:@"0" forKey:@"age"];
return YES;
}
if ([*ioValue floatValue] <= 0.0)
{
if (outError != NULL)
{
NSString *errorString = NSLocalizedStringFromTable(
@"年齡要大于0歲", @"人",
@"年齡錯誤");
NSDictionary *userInfoDict = @{ NSLocalizedDescriptionKey : errorString };
NSError *error = [[NSError alloc] initWithDomain:@"年齡校驗"
code:0
userInfo:userInfoDict];
*outError = error;
}
return NO;
}
else
{
return YES;
}
}
- (void)viewDidLoad {
[super viewDidLoad];
NSNumber *ageNum = [NSNumber numberWithInteger:0];
NSError *error = nil;
[self validateValue:&ageNum forKey:@"age" error:&error];
NSLog(@"error:%@",error);
}
輸出結(jié)果
2016-09-01 15:30:29.661 KVC[3044:197432] error:Error Domain=年齡校驗 Code=0 "年齡要大于0歲" UserInfo={NSLocalizedDescription=年齡要大于0歲}
五矮男、容器類
關(guān)于KVC在容器類中的應(yīng)用移必。容器類主要包括:NSDictionary、NSArray毡鉴、NSSet三種崔泵。關(guān)于容器類的操作方法有很多,分類整理一下
1眨补、如果作為對象的一個屬性值管削,那就作為對象屬性處理倒脓,無論Key還是KeyPath都符合前四條中說的規(guī)則撑螺;
2、就可變不可變來說崎弃,一般來說存什么取什么甘晤,但是可以根據(jù)需要獲取相應(yīng)的方法
@property (nonatomic,assign)NSMutableArray *mutableArray;
@property (nonatomic,assign)NSArray *array;
- (void)viewDidLoad {
[super viewDidLoad];
[self setValue:[NSArray arrayWithObjects:@"zwq", nil] forKey:@"array"];
[self setValue:[NSMutableArray arrayWithObjects:@"zwq2", nil] forKey:@"mutableArray"];
NSLog(@"不可變:%@--%@",[[self valueForKey:@"array"] class],[[self mutableArrayValueForKey:@"array"] class]);
NSLog(@"可變:%@--%@",[[self valueForKey:@"mutableArray"] class],[[self mutableArrayValueForKey:@"mutableArray"] class]);
}
輸出結(jié)果
2016-09-01 16:30:55.057 KVC[3328:231529] 不可變:__NSArrayI--NSKeyValueSlowMutableArray
2016-09-01 16:30:55.057 KVC[3328:231529] 可變:__NSArrayM--NSKeyValueSlowMutableArray
//KeyPath道理也是一樣的
3、需要單獨說的是NSDictionary跟NSArray有點不一樣饲做,而且功常用一點
//根據(jù)指定dic設(shè)置對象屬性值线婚。使用dic的key來標(biāo)識屬性,dic的value標(biāo)識值盆均,底層調(diào)用setValue:forKey:進(jìn)行賦值塞弊。
- (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *, id> *)keyedValues;
//獲取一組key的屬性值,然后以NSDictionary形式返回
- (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;
一個常見的功能應(yīng)用,獲取網(wǎng)絡(luò)數(shù)據(jù)游沿,數(shù)據(jù)解析完畢然后賦值的時候饰抒,如果Key很多是個很麻煩的事情,但是使用setValuesForKeysWithDictionary:
一行代碼搞定
//比如Model的屬性
@property (nonatomic,copy)NSString *name;
@property (nonatomic,copy)NSString *address;
- (void)viewDidLoad {
[super viewDidLoad];
//比如需要解析的數(shù)據(jù)
NSDictionary *dic =@{@"name":@"zwq",@"address":@"地球"};
[self setValuesForKeysWithDictionary:dic];
NSLog(@"name:%@--address:%@",self.name,self.address);
}
輸出結(jié)果
2016-09-01 16:42:47.898 KVC[3367:237574] name:zwq--address:地球
注意:
1诀黍、如果dic中有未定義的key那么需要進(jìn)行異常處理袋坑,參考《三、異常處理》段落眯勾。
2枣宫、容器類比如NSArray, NSSet, NSDictionary不能包含nil值,需要使用NSNull替換(一個表示nil值的單例類)
3吃环、方法dictionaryWithValuesForKeys:和setValuesForKeysWithDictionary:會自動轉(zhuǎn)換NSNull和nil也颤,不需要過多關(guān)注。
4郁轻、容器類運算符
容器類運算是valueForKeyPath:
中特殊的KeyPath歇拆,運算符跟在@符號之后,格式如下圖
整個KeyPath以運算符為中心范咨,分為3部分故觅。左邊的路徑標(biāo)識容器類(set或者array)的訪問路徑,中間是運算符渠啊,右邊是參加運算的屬性訪問路徑输吏。
暫不支持自定義運算符,總體分為三種替蛉;
分類 | 內(nèi)容 |
---|---|
基本運算符 | @avg(平均值)贯溅、@count(數(shù)量)、@max(最大值)躲查、 @min(最小值)它浅、@sum(求和) |
對象運算符 | @distinctUnionOfObjects(祛同屬性值集合)、@unionOfObjects(屬性值集合) |
容器運算符 | @distinctUnionOfArrays()镣煮、@unionOfArrays()姐霍、@distinctUnionOfSets() |
選擇其中一個演示一下,其它的運算符同理典唇。
//VC有一個數(shù)組屬性
@property (nonatomic,assign)NSArray *array;
- (void)viewDidLoad {
[super viewDidLoad];
//Data有一個name屬性
Data *data1 = [[Data alloc] init];
Data *data2 = [[Data alloc] init];
Data *data3 = [[Data alloc] init];
data1.name=@"data1";
data2.name=@"data2";
data3.name=@"data3";
//self.array.name
NSArray *arr = [NSArray arrayWithObjects:data1,data2,data1, nil];
[self setValue:arr forKey:@"array"];
NSArray *distinctArr = [self valueForKeyPath:@"array.@distinctUnionOfObjects.name"];
NSLog(@"distinctArr:%@",distinctArr);
NSArray *undistinctArr = [self valueForKeyPath:@"array.@unionOfObjects.name"];
NSLog(@"undistinctArr:%@",undistinctArr);
}
輸出結(jié)果
2016-09-01 17:17:59.049 KVC[3507:256556] distinctArr:(
data1,
data2
)
2016-09-01 17:17:59.050 KVC[3507:256556] undistinctArr:(
data1,
data2,
data1
)
以上問本人自己學(xué)習(xí)感悟镊折,理解并整理。更多內(nèi)容請查看官方文檔介衔。