KVC是苹果的键值编码技术,全称是Key-Value Coding。键值编码是NSKeyValueCoding非正式协议支持的一种机制,对象采用这种机制来提供对其属性的间接访问。通过KVC能快速获取和设置属性的值。
官方文档的地址
NSObject遵守NSKeyValueCoding协议,所有的继承自NSObject的对象都可以调用KVC方法。
@interface NSObject(NSKeyValueCoding)
...
@end
复制代码
1.用途
1.1访问对象属性
1.1.1通常设置属性是通过setter方法,KVC提供了更灵活的设置方法。如下
[myAccount setCurrentBalance:@(100.0)]
[myAccount setValue:@(100.0) forKey:@"currentBalance"];
复制代码
1.1.2通过键获取属性的API
➤valueForKey: 按照属性查找顺序查找,找到返回结果,找不到报错valueForUndefinedKey:
➤valueForKeyPath: 同上
➤dictionaryWithValuesForKeys:通过键(key)的数组获取对应的键值字典,可用作对象转字典。
例如:
DWZPerson *p1 = [DWZPerson new];
p1.name = @"rose";
p1.hobby = @"play";
NSDictionary *d = [p1 dictionaryWithValuesForKeys:@[@"name",@"hobby"]];
输出的结果是{hobby = play;name = rose;}
➤通过keyPath寻址的时候如果中间有一个值是集合,则返回集合所有后续的路径的值。如下
复制代码
1.1.3通过键设置属性的API
➤setValue:forKey: 如果key正确,设置值;不正确,setValue:forUndefinedKey:
➤setValue:forKeyPath:
➤setValuesForKeysWithDictionary: 常用作字典转模型。
复制代码
1.2访问集合属性
mutableArrayValueForKey:和mutableArrayValueForKeyPath:
它们返回行为类似于NSMutableArray对象的代理对象。
mutableSetValueForKey:和mutableSetValueForKeyPath:
它们返回行为类似于NSMutableSet对象的代理对象。
mutableOrderedSetValueForKey:和mutableOrderedSetValueForKeyPath:
它们返回行为类似于NSMutableOrderedSet对象的代理对象。
操作(增加、删除、替换)代理对象,协议的实现会相应的修改基础属性。比使用
valueForKey:获取一个非可变的集合对象,创建一个内容已更改的修改对象,然后使用
setValue:forKey:消息将其存储回该对象更为有效。
复制代码
1.3使用集合操作符
1.3.1组操作符包括:@avg,@count,@max,@min,@sum
//属性amount的平均值
NSNumber *transactionAverage = [self.transactions valueForKeyPath:@"@avg.amount"];
//transactions的条数
NSNumber *numberOfTransactions = [self.transactions valueForKeyPath:@"@count"];
//属性date的最大/最小值
NSDate *latestDate = [self.transactions valueForKeyPath:@"@max.date"];
NSDate *earliestDate = [self.transactions valueForKeyPath:@"@min.date"];
//属性amount求和
NSNumber *amountSum = [self.transactions valueForKeyPath:@"@sum.amount"];
复制代码
1.3.2数组操作符
重要:如果子对象是nil使用数组操作符会导致valueForKeyPath:方法报错。
数组操作符包括:
@distinctUnionOfObjects 返回不同的对象集合(相当于去重操作)
NSArray *distinctPayees = [self.transactions valueForKeyPath:@"@distinctUnionOfObjects.payee"];
@unionOfObjects 返回所有结果
NSArray *payees = [self.transactions valueForKeyPath:@"@unionOfObjects.payee"];
复制代码
1.3.3嵌套操作符:对嵌套集合进行操作
NSArray* arrayOfArrays = @[self.transactions, moreTransactions];
@distinctUnionOfArrays
NSArray *collectedDistinctPayees = [arrayOfArrays valueForKeyPath:@"@distinctUnionOfArrays.payee"];
@unionOfArrays
NSArray *collectedPayees = [arrayOfArrays valueForKeyPath:@"@unionOfArrays.payee"];
@distinctUnionOfSets
复制代码
1.4KVC在非对象中的表现
KVC能同时作用在OC对象和基本数据结构上,KVC的实现能自动在对象和基本数据结构上转换。如果设置nil给非对象属性,就会调用setNilValueForKey:方法,没有重写该方法就会报错。
1.4.1基本数据类型都是转换NSNumber,例如:BOOL,char,int,double,float,long,short
[p1 setValue:@(20) forKey:@"age"];
[p1 setValue:@(true) forKey:@"isfemale"];
BOOL v3 = [p1 valueForKey:@"isfemale"];
NSNumber *v4 = [p1 valueForKey:@"age"];
复制代码
1.4.2 结构体类型都是转换成NSValue,例如NSPoint, NSRange, NSRect, NSSize
对于自定义的结构体,使用如下:
typedef struct {
float x, y, z;
} ThreeFloats;
@interface MyClass
@property (nonatomic) ThreeFloats threeFloats;
@end
NSValue* result = [myClass valueForKey:@"threeFloats"];
ThreeFloats floats = {1., 2., 3.};
NSValue* value = [NSValue valueWithBytes:&floats objCType:@encode(ThreeFloats)];
[myClass setValue:value forKey:@"threeFloats"];
复制代码
1.5验证属性
//KVC提供属性值正确性验证的API,它可以用来检查set的值是否正确、为不正确的值做一个替换值或者拒绝设置新值并返回错误原因
- (BOOL)validateValue:(inout id _Nullable * _Nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;
复制代码
2 KVC原理
2.1 get方法的查找顺序
1 get<Key>, <key>, is<Key>, _<key> 查找顺序,找到跳到第5步
2 判断是否包含countOf<Key>,objectIn<Key>AtIndex:,<key>AtIndexes: 如果包含第一个方法和后两个方法中的一个就确定是数组,创建一个NSArray对象并返回;否则到第3步。
3判断是否同时包含countOf <Key>,enumeratorOf <Key>,memberOf <Key>:方法,包含就判断是NSSet,创建NSSet对象并返回,否则跳到第4步.
4 accessInstanceVariablesDirectly 判断返回值,为YES,按顺序查找实例变量_<key>, _is<Key>, <key>, is<Key>找到跳第5步;否则到第6步。
5 如果查找到的值是对象指针,返回结果;如果是基本数据类型,转换成NSNumber并返回;如果不是基本数据类型,转换成NSValue并返回。
6 所有方法都失败,调用valueForUndefinedKey:方法,如果该类的继承链上没实现该方法,就抛出错误。
复制代码
2.2 set的方法查找顺序
1 设值顺序:set<Key>,_set<Key>,setIs<Key>,找到就调用方法并返回
2 accessInstanceVariablesDirectly 判断返回值,为YES,设值实例变量并返回;为NO,到第4步。
3 设值实例变量的顺序:_<key>, _is<Key>, <key>, is<Key>,有相应的实例变量则设置并返回,没有找到的话到第4步。
4 调用setValue:forUndefinedKey:方法,继承链没实现方法就抛出错误。
复制代码
3 自定义KVO
3.1根据原理自定义GET方法
/// 自定义KVCGet方法
/// @param key key
- (nullable id)dwz_GETValueForKey:(NSString *)key{
// 1:刷选key 判断非空
if (key == nil || key.length == 0) {
return nil;
}
// 2:找到相关方法 get<Key> <key> countOf<Key> objectIn<Key>AtIndex
// key 要大写
NSString *Key = key.capitalizedString;
// 拼接方法
NSString *getKey = [NSString stringWithFormat:@"get%@",Key];
NSString *countOfKey = [NSString stringWithFormat:@"countOf%@",Key];
NSString *objectInKeyAtIndex = [NSString stringWithFormat:@"objectIn%@AtIndex:",Key];
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
if ([self respondsToSelector:NSSelectorFromString(getKey)]) {
return [self performSelector:NSSelectorFromString(getKey)];
}else if ([self respondsToSelector:NSSelectorFromString(key)]){
return [self performSelector:NSSelectorFromString(key)];
}else if ([self respondsToSelector:NSSelectorFromString(countOfKey)]){
if ([self respondsToSelector:NSSelectorFromString(objectInKeyAtIndex)]) {
int num = (int)[self performSelector:NSSelectorFromString(countOfKey)];
NSMutableArray *mArray = [NSMutableArray arrayWithCapacity:1];
for (int i = 0; i<num-1; i++) {
num = (int)[self performSelector:NSSelectorFromString(countOfKey)];
}
for (int j = 0; j<num; j++) {
id objc = [self performSelector:NSSelectorFromString(objectInKeyAtIndex) withObject:@(num)];
[mArray addObject:objc];
}
return mArray;
}
}
#pragma clang diagnostic pop
// 3:判断是否能够直接赋值实例变量
if (![self.class accessInstanceVariablesDirectly] ) {
@throw [NSException exceptionWithName:@"LGUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ valueForUndefinedKey:]: this class is not key value coding-compliant for the key name.****",self] userInfo:nil];
}
// 4.找相关实例变量进行赋值
// 4.1 定义一个收集实例变量的可变数组
NSMutableArray *mArray = [self getIvarListName];
// _<key> _is<Key> <key> is<Key>
// _name -> _isName -> name -> isName
NSString *_key = [NSString stringWithFormat:@"_%@",key];
NSString *_isKey = [NSString stringWithFormat:@"_is%@",Key];
NSString *isKey = [NSString stringWithFormat:@"is%@",Key];
if ([mArray containsObject:_key]) {
Ivar ivar = class_getInstanceVariable([self class], _key.UTF8String);
return object_getIvar(self, ivar);;
}else if ([mArray containsObject:_isKey]) {
Ivar ivar = class_getInstanceVariable([self class], _isKey.UTF8String);
return object_getIvar(self, ivar);;
}else if ([mArray containsObject:key]) {
Ivar ivar = class_getInstanceVariable([self class], key.UTF8String);
return object_getIvar(self, ivar);;
}else if ([mArray containsObject:isKey]) {
Ivar ivar = class_getInstanceVariable([self class], isKey.UTF8String);
return object_getIvar(self, ivar);;
}
return @"";
}
复制代码
3.2根据原理自定义SET方法
/// 自定义KVC方法
/// @param value key
/// @param key value
-(void)dwz_SetValue:(NSString *)value forKey:(NSString *)key {
// 1.判断key是否有值
if (key == nil || key.length == 0) {
NSLog(@"dwz_SetValue error with key is nil");
return;
}
// 2.判断是否有setter方法
NSString *k = key.capitalizedString;
NSString *setKey = [NSString stringWithFormat:@"set%@:",k];
NSString *_setKey = [NSString stringWithFormat:@"_set%@:",k];
NSString *setIsKey = [NSString stringWithFormat:@"setIs%@:",k];
// setKey方法调用
if ([self dwz_PerformSelectorWithMethodName:setKey value:value]) {
NSLog(@"%s -%@",__func__,setKey);
return;
}
// _setKey方法调用
if ([self dwz_PerformSelectorWithMethodName:_setKey value:value]) {
NSLog(@"%s -%@",__func__,_setKey);
return;
}
// _setIsKey方法调用
if ([self dwz_PerformSelectorWithMethodName:setIsKey value:value]) {
NSLog(@"%s -%@",__func__,setIsKey);
return;
}
// 3.判断是否允许直接访问成员变量
if (![[self class] accessInstanceVariablesDirectly]) {
@throw [NSException exceptionWithName:@"DWZUnknownKeyException" reason:[NSString stringWithFormat:@"DWZ [%@ setValueForUndefineKey:]%@",self,key] userInfo:nil];
return;
}
// 4.依次判断是否有成员变量并设置相应的值
NSMutableArray *ivars = [self getIvarListName];
NSString *_key = [NSString stringWithFormat:@"_%@",key];
NSString *_isKey = [NSString stringWithFormat:@"_is%@",k];
NSString *isKey = [NSString stringWithFormat:@"is%@",k];
if ([ivars containsObject:_key]) {
Ivar ivar = class_getInstanceVariable([self class], _key.UTF8String);
object_setIvar(self, ivar, value);
return;
}
if ([ivars containsObject:_isKey]) {
Ivar ivar = class_getInstanceVariable([self class], _isKey.UTF8String);
object_setIvar(self, ivar, value);
return;
}
if ([ivars containsObject:key]) {
Ivar ivar = class_getInstanceVariable([self class], key.UTF8String);
object_setIvar(self, ivar, value);
return;
}
if ([ivars containsObject:isKey]) {
Ivar ivar = class_getInstanceVariable([self class], isKey.UTF8String);
object_setIvar(self, ivar, value);
return;
}
// 此处是这样处理,正确的应该是调用setValueForUndefineKey:方法
@throw [NSException exceptionWithName:@"DWZUnknownKeyException" reason:[NSString stringWithFormat:@"DWZ [%@ %@]%@",self,NSStringFromSelector(_cmd),key] userInfo:nil];
}
复制代码
4. 使用场景和注意
4.1 字典和模型的互转
//字典转模型
- (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *, id> *)keyedValues;
//模型转字典
- (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;
复制代码
4.2 设置隐藏的属性值
例如UITextField的 placeHolderAttributeText
复制代码
4.3 操作集合数据
- (void)transmitMsg{
NSArray *arrStr = @[@"english", @"franch", @"chinese"];
NSArray *arrCapStr = [arrStr valueForKey:@"capitalizedString"];
for (NSString *str in arrCapStr) {
NSLog(@"%@", str);
}
NSArray *arrCapStrLength = [arrCapStr valueForKeyPath:@"capitalizedString.length"];
for (NSNumber *length in arrCapStrLength) {
NSLog(@"%ld", (long)length.integerValue);
}
}
2021-04-27 17:50:47.982831+0800 KVC[1678:58128] English
2021-04-27 17:50:47.983082+0800 KVC[1678:58128] Franch
2021-04-27 17:50:47.983330+0800 KVC[1678:58128] Chinese
2021-04-27 17:50:47.985906+0800 KVC[1678:58128] 7
2021-04-27 17:50:47.986096+0800 KVC[1678:58128] 6
2021-04-27 17:50:47.986247+0800 KVC[1678:58128] 7
复制代码
4.3 实现setValueForUndefineKey: 和 setNilValueForKey: 方法,防止设值异常崩溃。
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END