KVC
全稱(chēng)是Key Value Coding
门烂,在NSKeyValueCoding.h
非正式協(xié)議文件中,聲明了KVC
能使用的方法剃诅,KVC
提供一種通過(guò)字符串來(lái)訪(fǎng)問(wèn)類(lèi)中的屬性和成員變量的方法朝墩。
-
NSKeyValueCoding協(xié)議中的方法
NSKeyValueCoding.gif
從NSKeyValueCoding協(xié)議方法說(shuō)去
先來(lái)看看幾個(gè)宏
當(dāng)調(diào)用KVC時(shí)key值為空時(shí)侮东,就會(huì)拋出這個(gè)異常粱年。
FOUNDATION_EXPORT NSExceptionName const NSUndefinedKeyException;
// NSKeyValueCoding中的運(yùn)算符
NSKeyValueOperator const NSAverageKeyValueOperator; // 求平均值
NSKeyValueOperator const NSCountKeyValueOperator; // 統(tǒng)計(jì)總數(shù)
NSKeyValueOperator const NSDistinctUnionOfArraysKeyValueOperator; // 獲取嵌套數(shù)組中不同的值
NSKeyValueOperator const NSDistinctUnionOfObjectsKeyValueOperator; // 獲取不同的值
NSKeyValueOperator const NSDistinctUnionOfSetsKeyValueOperator; // 獲取嵌套集合中不同的值
NSKeyValueOperator const NSMaximumKeyValueOperator; // 獲取最大值
NSKeyValueOperator const NSMinimumKeyValueOperator; // 獲取最小值
NSKeyValueOperator const NSSumKeyValueOperator; // 求和
NSKeyValueOperator const NSUnionOfArraysKeyValueOperator; // 獲取嵌套數(shù)組中的值售滤,不去重
NSKeyValueOperator const NSUnionOfObjectsKeyValueOperator; // 獲取所有的值,不去重
NSKeyValueOperator const NSUnionOfSetsKeyValueOperator; // 獲取嵌套集合中的值台诗,不去重
這些運(yùn)算符能幫助我們快速的運(yùn)算集合中的操作完箩。在官方文檔中教了我們?cè)趺词褂眠@些操作符。
NSArray *array = @[@"1",@"1",@"1",@"2",@"2",@"3",@"3",@"4",@"5",@"5"];
NSArray *array1 = @[@"3",@"4",@"5",@"5",@"6",@"6",@"7",@"8",@"8"];
NSArray *unionArray = @[array,array1];
- 獲取數(shù)組的個(gè)數(shù)拉庶,這個(gè)一般沒(méi)有嗜憔,因?yàn)閿?shù)組就要count屬性。
NSString *count = [array valueForKeyPath:@"@count"];
NSLog(@"數(shù)組個(gè)數(shù) = %@",count);
2018-06-19 15:50:20.568834+0800 KVC和NSKeyValueCoding[10417:285376] 數(shù)組個(gè)數(shù) = 10
- 獲取數(shù)組的平均值
NSString *avg = [array valueForKeyPath:@"@avg.self"];
NSLog(@"數(shù)組平均值 = %@",avg);
2018-06-19 15:50:20.569342+0800 KVC和NSKeyValueCoding[10417:285376] 數(shù)組平均值 = 2.7
- 獲取去重后的數(shù)組氏仗。如果有一道題目是讓我們從一萬(wàn)個(gè)重復(fù)的數(shù)據(jù)中找出不同的數(shù)吉捶,就可以用這個(gè)方法。當(dāng)然也可以數(shù)組轉(zhuǎn)NSSet去重皆尔,再轉(zhuǎn)NSArray排序呐舔。
NSArray *distinct = [array valueForKeyPath:@"@distinctUnionOfObjects.self"];
NSArray *distinctUnionOfObjects = [distinct sortedArrayUsingComparator:^NSComparisonResult(id _Nonnull obj1, id _Nonnull obj2) {
return [obj1 compare:obj2];
}]; //數(shù)組正序
NSLog(@"去重后的數(shù)組 = %@",distinctUnionOfObjects);
2018-06-19 15:50:20.569594+0800 KVC和NSKeyValueCoding[10417:285376] 去重后的數(shù)組 = (
1,
2,
3,
4,
5
)
- 數(shù)組數(shù)字最大值
NSString *max = [array valueForKeyPath:@"@max.self"];
NSLog(@"數(shù)組數(shù)字最大值 = %@",max);
2018-06-19 15:50:20.569777+0800 KVC和NSKeyValueCoding[10417:285376] 數(shù)組數(shù)字最大值 = 5
- 數(shù)組數(shù)字最小值
NSString *min = [array valueForKeyPath:@"@min.self"];
NSLog(@"數(shù)組數(shù)字最小值 = %@",min);
2018-06-19 15:50:20.569928+0800 KVC和NSKeyValueCoding[10417:285376] 數(shù)組數(shù)字最小值 = 1
- 獲取數(shù)組數(shù)字總和
NSString *sum = [array valueForKeyPath:@"@sum.self"];
NSLog(@"總和 = %@", sum);
2018-06-19 15:50:20.570135+0800 KVC和NSKeyValueCoding[10417:285376] 總和 = 27
- 不去重的數(shù)組,這個(gè)在開(kāi)發(fā)中也沒(méi)用慷蠕,因?yàn)榫退銛?shù)組本身珊拼。
NSArray *unionOfObjects = [array valueForKeyPath:@"@unionOfObjects.self"];
NSLog(@"不去重的數(shù)組 = %@", unionOfObjects);
2018-06-19 15:50:20.570313+0800 KVC和NSKeyValueCoding[10417:285376] 不去重的數(shù)組 = (
1,
1,
1,
2,
2,
3,
3,
4,
5,
5
)
- 多個(gè)數(shù)組去重。去重后變一個(gè)數(shù)組流炕。
NSArray *distinctUnionOfArrays = [unionArray valueForKeyPath:@"@distinctUnionOfArrays.self"];
NSLog(@"多個(gè)數(shù)組去重后 = %@",distinctUnionOfArrays);
2018-06-19 15:50:20.570545+0800 KVC和NSKeyValueCoding[10417:285376] 多個(gè)數(shù)組去重后 = (
2,
3,
4,
5,
6,
7,
8,
1
)
- 多個(gè)數(shù)組不去重澎现,這個(gè)在開(kāi)發(fā)中被可變數(shù)組的addObjectsFromArray方法替代.
NSArray *unionOfArrays = [unionArray valueForKeyPath:@"@unionOfArrays.self"];
NSLog(@"多個(gè)數(shù)組不去重 = %@",unionOfArrays);
2018-06-19 15:50:20.570716+0800 KVC和NSKeyValueCoding[10417:285376] 多個(gè)數(shù)組不去重 = (
1,
1,
1,
2,
2,
3,
3,
4,
5,
5,
3,
4,
5,
5,
6,
6,
7,
8,
8
)
賦值和取值操作
- 創(chuàng)建一個(gè) Animal 自定義對(duì)象
#import <Foundation/Foundation.h>
@class Food;
/**
對(duì)象
*/
@interface Animal : NSObject
// 動(dòng)物姓名
@property (nonatomic, copy) NSString *name;
// 動(dòng)物年齡
@property (nonatomic, assign) NSInteger age;
// 食物
@property (nonatomic, strong) Food *food;
@end
@interface Food : NSObject
// 水果
@property (nonatomic, copy) NSString *fruit;
// 肉
@property (nonatomic, copy) NSString *meat;
@end
#import "Animal.h"
@implementation Animal
- (Food *)food {
if (!_food) {
_food = [[Food alloc] init];
}
return _food;
}
@end
@implementation Food
@end
- 使用
- setValue: forKey:
賦值仅胞,valueForKey:
方法取值。直接通過(guò)Animal中的屬性名來(lái)作為key賦值取值剑辫。
[_animal setValue:@"小妹" forKey:@"name"];
NSLog(@"name = %@",[_animal valueForKey:@"name"]);
2018-06-19 17:13:32.029108+0800 KVC和NSKeyValueCoding[11681:325413] name = 小妹
- 如果想對(duì)
Animal
對(duì)象中的Food
對(duì)象的fruit
屬性賦值干旧,這種多級(jí)訪(fǎng)問(wèn)設(shè)置或獲取value
就要用到keyPath
來(lái)存取。
[_animal setValue:@"蘋(píng)果" forKeyPath:@"food.fruit"];
NSLog(@"fruit = %@",[_animal valueForKeyPath:@"food.fruit"]);
2018-06-19 17:34:33.650983+0800 KVC和NSKeyValueCoding[11746:338536] fruit = 蘋(píng)果
如果用- setValue: forKey:
存取妹蔽,則會(huì)造成crash
[_animal setValue:@"蘋(píng)果" forKey:@"food.fruit"];
NSLog(@"fruit = %@",[_animal valueForKey:@"food.fruit"]);
Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<Animal 0x60000003cdc0> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key food.fruit.'
多值的賦值和取值
- 使用的方法
- (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;
- (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *, id> *)keyedValues;
- 例子
[_animal setValuesForKeysWithDictionary:@{@"name":@"小馬哥" , @"age" : @(25)}];
NSLog(@"name = %@",[_animal valueForKey:@"name"]);
NSLog(@"age = %li",_animal.age);
NSLog(@"賦值后為 %@",[_animal dictionaryWithValuesForKeys:@[@"name",@"age"]]);
- 方法輸出
2018-06-20 09:19:00.728542+0800 KVC和NSKeyValueCoding[12597:503862] name = 小馬哥
2018-06-20 09:19:00.728667+0800 KVC和NSKeyValueCoding[12597:503862] age = 25
2018-06-20 09:19:00.728941+0800 KVC和NSKeyValueCoding[12597:503862] 賦值后為 {
age = 25;
name = "\U5c0f\U9a6c\U54e5";
}
- 項(xiàng)目中椎眯,我們字典轉(zhuǎn)模型通常是
// 初始化賦值
- (instancetype)initWithDic:(NSDictionary *)dic {
if (self = [super init]) {
self.age = [dic[@"age"] integerValue];
self.name = dic[@"name"];
}
return self;
}
但是當(dāng)我們知道了多值操作的方法,并且model
的屬性和字典的key
相同胳岂,我們可以使用下面方法编整,減少賦值的多余代碼。
// 初始化賦值
- (instancetype)initWithDic:(NSDictionary *)dic {
if (self = [super init]) {
[self setValuesForKeysWithDictionary:dic];
}
return self;
}
// 重寫(xiě) setValuesForKeysWithDictionary方法 去除 nil和null
- (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *,id> *)keyedValues {
for (id key in keyedValues.allKeys) {
if (![keyedValues valueForKey:key]) {
[self setValue:@"" forKey:key];
} else if ([[keyedValues valueForKey:key] isEqual:[NSNull null]]) {
[self setValue:@"" forKey:key];
} else {
[self setValue:[keyedValues valueForKey:key] forKey:key];
}
}
}
集合getter
在對(duì)集合對(duì)象進(jìn)行getter
操作時(shí)乳丰,可以調(diào)用一下方法通過(guò)key
或者keyPath
獲取掌测。如果再對(duì)容器類(lèi)進(jìn)行add
或remove
等操作時(shí),就會(huì)觸發(fā)KVO
的消息通知
- key
- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;
- (NSMutableOrderedSet *)mutableOrderedSetValueForKey:(NSString *)key API_AVAILABLE(macos(10.7), ios(5.0), watchos(2.0), tvos(9.0));
- (NSMutableSet *)mutableSetValueForKey:(NSString *)key;
- keypath
- (NSMutableArray *)mutableArrayValueForKeyPath:(NSString *)keyPath;
- (NSMutableOrderedSet *)mutableOrderedSetValueForKeyPath:(NSString *)keyPath API_AVAILABLE(macos(10.7), ios(5.0), watchos(2.0), tvos(9.0));
- (NSMutableSet *)mutableSetValueForKeyPath:(NSString *)keyPath;
屬性驗(yàn)證
- 相對(duì)應(yīng)的也有key和keyPath對(duì)應(yīng)的兩個(gè)方法成艘。用來(lái)驗(yàn)證傳入的value和key是否正確赏半。
- (BOOL)validateValue:(inout id _Nullable * _Nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;
- (BOOL)validateValue:(inout id _Nullable * _Nonnull)ioValue forKeyPath:(NSString *)inKeyPath error:(out NSError **)outError;
// 驗(yàn)證碼`key value` 是否滿(mǎn)足需求
- (BOOL)validateValue:(inout id _Nullable __autoreleasing *)ioValue forKey:(NSString *)inKey error:(out NSError * _Nullable __autoreleasing *)outError {
if ([inKey isEqualToString:@"age"]) {
if ([*ioValue integerValue] < 18) {
*outError = [NSError errorWithDomain:AnimalErrorDomain code:10001 userInfo:@{NSLocalizedDescriptionKey : @"小動(dòng)物還未成年"}];
return NO;
}
}
return YES;
}
NSNumber *age = @(12);
NSError *error;
if ([_animal validateValue:&age forKey:@"age" error:&error]) {
NSLog(@"小動(dòng)物成年了");
} else {
NSLog(@"error = %@",error);
}
2018-06-20 09:07:57.458669+0800 KVC和NSKeyValueCoding[12550:497482] error = Error Domain=AnimalErrorDomain Code=10001 "小動(dòng)物還未成年" UserInfo={NSLocalizedDescription=小動(dòng)物還未成年}
KVC的底層實(shí)現(xiàn)
為了方便閱讀,將實(shí)例變量放在了.h文件中淆两。
賦值流程
1 先調(diào)用屬性的setter方法完成賦值断箫。
2 如果沒(méi)有發(fā)現(xiàn)setter方法,則檢查+ (BOOL)accessInstanceVariablesDirectly
方法秋冰,默認(rèn)返回YES
仲义,這是在沒(méi)有找到存取器的時(shí)候才調(diào)用的方法。返回為YES表示查找_<key>
剑勾、_is<Key>
埃撵、<key>
、is<Key>
找value
虽另,如果有則賦值暂刘。
3 如果都沒(méi)有找到,則會(huì)調(diào)用setValue:forUndefinedKey:
方法并拋出異常捂刺。測(cè)試
.h文件
#import <Foundation/Foundation.h>
@class Food;
/**
對(duì)象
*/
@interface Animal : NSObject {
NSString *name;
NSString *_name;
NSString *_isName;
NSString *isName;
}
// 動(dòng)物姓名
@property (nonatomic, copy) NSString *name;
@end
.m文件
#pragma mark --- KVC的底層實(shí)現(xiàn)測(cè)試
// 是否可以按照 _<key>谣拣、_is<Key>、<key>族展、is<Key> 方式查找
+ (BOOL)accessInstanceVariablesDirectly {
return NO;
}
// setter
- (void)setName:(NSString *)name {
NSLog(@"調(diào)用了setter方法");
_name = name;
}
// 重寫(xiě)setValue: forKey: 用來(lái)查看當(dāng)前的key森缠。具體是_name,_isName,name,isName.
- (void)setValue:(id)value forKey:(NSString *)key {
NSLog(@"%@", [NSString stringWithFormat:@"當(dāng)前調(diào)用的key %@",key]);
[super setValue:value forKey:key];
}
// kvc底層實(shí)現(xiàn)驗(yàn)證
Animal *animal = [[Animal alloc] init];
[animal setValue:@"曉東" forKey:@"name"];
NSLog(@"name = %@",[animal valueForKey:@"name"]);
打印
2018-06-20 13:38:45.541011+0800 KVC和NSKeyValueCoding[14274:650523] 當(dāng)前調(diào)用的key name
2018-06-20 13:38:45.541134+0800 KVC和NSKeyValueCoding[14274:650523] 調(diào)用了setter方法
2018-06-20 13:38:45.541251+0800 KVC和NSKeyValueCoding[14274:650523] name = 曉東
現(xiàn)在把屬性name去掉,這樣就不會(huì)默認(rèn)生成存取器方法仪缸。因?yàn)閍ccessInstanceVariablesDirectly為NO贵涵,所有會(huì)走setValue:(id)value forUndefinedKey:(NSString *)key
方法。我們?nèi)绻幌胱屚饨缭L(fǎng)問(wèn)類(lèi)的成員變量,則可以將accessInstanceVariablesDirectly屬性賦值為NO宾茂。
@interface Animal : NSObject {
NSString *name;
NSString *_name;
NSString *_isName;
NSString *isName;
}
// 動(dòng)物姓名
//@property (nonatomic, copy) NSString *name;
#pragma mark --- KVC的底層實(shí)現(xiàn)測(cè)試
// 是否可以按照 _<key>腾降、_is<Key>链烈、<key>闲昭、is<Key> 方式查找
+ (BOOL)accessInstanceVariablesDirectly {
return NO;
}
// setter
//- (void)setName:(NSString *)name {
// NSLog(@"調(diào)用了setter方法");
// _name = name;
//}
// 重寫(xiě)setValue: forKey: 用來(lái)查看當(dāng)前的key栖榨。具體是_name,_isName,name,isName.
- (void)setValue:(id)value forKey:(NSString *)key {
NSLog(@"%@", [NSString stringWithFormat:@"當(dāng)前調(diào)用的key %@",key]);
[super setValue:value forKey:key];
}
2018-06-20 13:49:54.816830+0800 KVC和NSKeyValueCoding[14428:661962] 當(dāng)前調(diào)用的key name
2018-06-20 13:49:54.823548+0800 KVC和NSKeyValueCoding[14428:661962] *** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<Animal 0x604000277500> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key name.'
接下來(lái)把a(bǔ)ccessInstanceVariablesDirectly變成YES,因?yàn)榇嬖赺name,_isName,name,isName四個(gè)實(shí)例變量坟奥,所以會(huì)找到相應(yīng)的key并將value賦值。依次注釋操作
@interface Animal : NSObject {
NSString *name;
// NSString *_name;
// NSString *_isName;
// NSString *isName;
}
@interface Animal : NSObject {
// NSString *name;
NSString *_name;
// NSString *_isName;
// NSString *isName;
}
@interface Animal : NSObject {
// NSString *name;
// NSString *_name;
NSString *_isName;
// NSString *isName;
}
@interface Animal : NSObject {
// NSString *name;
// NSString *_name;
// NSString *_isName;
NSString *isName;
}
打印出來(lái)的都是
2018-06-20 13:55:08.241934+0800 KVC和NSKeyValueCoding[14468:666416] 當(dāng)前調(diào)用的key name
2018-06-20 13:56:40.147144+0800 KVC和NSKeyValueCoding[14497:668042] name = 曉東
當(dāng)實(shí)例變量_name,_isName,name,isName都不存在時(shí)并且setter方法不存在,
NSKeyValueCoding[14428:661962] 當(dāng)前調(diào)用的key name
2018-06-20 13:49:54.823548+0800 KVC和NSKeyValueCoding[14428:661962] *** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<Animal 0x604000277500> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key name.'
錯(cuò)誤處理
-
key
值的錯(cuò)誤
上面一直報(bào)的一個(gè)異常setValue:forUndefinedKey:
拇厢,我們可以通過(guò)重寫(xiě)這個(gè)方法來(lái)停止報(bào)這個(gè)異常爱谁。
-(void)setValue:(id)value forUndefinedKey:(NSString *)key{
NSLog(@"賦值失敗,該key不存在%@",key);
}
重寫(xiě)后打印孝偎。
2018-06-20 14:05:23.053180+0800 KVC和NSKeyValueCoding[14535:674635] 賦值失敗访敌,該key不存在name