前言
最近在写关于KVC、KVO的一些东西,也许很多人都认为KVC再简单不过了,其实不是这个样子的,因为我以前跟大部分人的想法是一样的,KVC、KVO固定的书写模式,调用也超级简单。其实不是的,因为越是简单的东西,越是容易被人忽略。现在就给大家分享一些关于KVC你可能不知道的东西。
KVC的赋值原理
setValue:forKey:赋值原理如下:
- 去模型中查找有没有对应的setter方法:例如:setIcon方法,有就直接调用这个setter方法给模型这个属性赋值[self setIcon:dic[@"icon"]];
- 如果找不到setter方法,接着就会去寻找有没有icon属性,如果有,就直接访问模型中的icon属性,进行赋值,icon=dict[@"icon"];
- 如果找不到icon属性,接着又会去寻找_icon属性,如果有,直接进行赋值_icon=dict[@"icon"];
- 如果都找不到就会报错:[<Flag 0X7fb74bc7a2c0> setValue:forUndefinedKey:]
扩展
读者可以去查查KVV(键值验证),进一步理解报错原因与容错方法。
KVC开关及检查
-
如果对某个类,不允许使用KVC,可以通过设置 accessInstanceVariablesDirectly 控制。
// 在该类的内部,重写此方法,外部使用KVC时,禁用没有写set get 方法的属性值。 // 注意:对于 @property 定义的属性可以 KVC+ -(BOOL)accessInstanceVariablesDirectly{ return NO; }
-
赋值检查
// 在类的内部,进行检查,不符合要求 返回NO ,提供外部参考。
- (BOOL)validateValue:(inout id _Nullable __autoreleasing *)ioValue forKey:(NSString *)inKey error:(out NSError * _Nullable __autoreleasing )outError{
if ([inKey isEqualToString:@"colors"] && [ioValue isKindOfClass:[NSArray class]]) {
return YES;
} else {
return NO;
}
}
//用法:
// 外部 使用时,先判断是否符合要求,再使用KVC。
NSError *error;
NSString *apoint = @"name";
if ([aPerson validateValue:&apoint forKey:@"_colors" error:&error]) {
NSLog(@"可以赋值 apoint");
[aPerson setValue:apoint forKey:@"_colors"];
} else {
NSLog(@"不可以赋值 apoint");
NSLog(@"%@",error.debugDescription);
}
KVC通过Dictionary为模型赋值(与昨天的有极大的差别)
昨天:昨天的情况,后台返回的json与模型变量名一致。
今天:如果后台给我们返回的一些json字符串,与我们的model属性一部分不一致(或者json包含关键字)。
简单的说:如果json里面的某些key就是和object的property名字不一样呢,或者有些server返回的字段是objc保留字如”id”, “description”等, 我们也希望也map dict to object, 这时候我们就需要用上setValue:forUndefinedKey, 因为如果我们不处理这些Undefined Key,还是用setValuesForKeysWithDictionary就会 抛出异常。
-(void)setValue:(id)value forUndefinedKey:(NSString *)key
{
if([key isEqualToString:@"nameXXX"])
self.name = value;
if([key isEqualToString:@"ageXXX"]) {
self.age = value;
}else{
[super setValue:value forKey:key];
}
}
所以只要重载这个方法,就可以处理了那些无法跟property相匹配的key了,默认的实现是抛出一个NSUndefinedKeyException。
KVC处理包含类内部包含类的情况(重写setValue:forKey:)
如果这时候server返回的People有了内嵌的json(如Products{product1{count:xx, sumPrice:xx}}, product2{} ….),又该怎么办,能把这个内嵌的json转化成我们的客户端的Product类嘛, 当然可以这时候就需要重载setValue:forKey, 单独处理”Products”这个key, 把它wrapper成我们需要的class
-(void) setValue:(id)value forKey:(NSString *)key
{
if([key isEqualToString:@"products"])
{
for(NSMutableDictionary *productDict in value)
{
Prodcut *product = [[Product alloc] initWithDictionary:prodcutDict];
[self.products addObject:product];
}
}
}
从这一点中可以知道,setValuesForKeysWithDictionary内部调用了setValue:forKey:方法。
keyPath的巧妙用法
情景:我们需要把一个数组里的People的名字的首字母大写,并且把新的名字存入新的数组, 这时候通常做法会是遍历整个数组,然后把每个People的name取出来,调用 capitalizedString 然后把新的String加入新的数组中。 有了KVC就有了新做法:
[array valueForKeyPath:@"name.capitalizedString"]
疑问:为什么用valueForKeyPath, 不用valueForKey?
因为:valueForKeyPath可以传递关系,例如这里是每个People的name property的String的capitalizedString property, 而valueForKey不能传递这样的关系,所以对于dict里面的dict, 我们也只能用valueForKeyPath。
KVC的一些其他用法
FOUNDATION_EXPORT NSString *const NSUndefinedKeyException;
FOUNDATION_EXPORT NSString *const NSAverageKeyValueOperator;
FOUNDATION_EXPORT NSString *const NSCountKeyValueOperator;
FOUNDATION_EXPORT NSString *const NSDistinctUnionOfArraysKeyValueOperator;
FOUNDATION_EXPORT NSString *const NSDistinctUnionOfObjectsKeyValueOperator;
FOUNDATION_EXPORT NSString *const NSDistinctUnionOfSetsKeyValueOperator;
FOUNDATION_EXPORT NSString *const NSMaximumKeyValueOperator;
FOUNDATION_EXPORT NSString *const NSMinimumKeyValueOperator;
FOUNDATION_EXPORT NSString *const NSSumKeyValueOperator;
FOUNDATION_EXPORT NSString *const NSUnionOfArraysKeyValueOperator;
FOUNDATION_EXPORT NSString *const NSUnionOfObjectsKeyValueOperator;
FOUNDATION_EXPORT NSString *const NSUnionOfSetsKeyValueOperator;
@interface NSObject(NSKeyValueCoding)
- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;
- (NSMutableOrderedSet *)mutableOrderedSetValueForKey:(NSString *)key NS_AVAILABLE(10_7, 5_0);
- (NSMutableSet *)mutableSetValueForKey:(NSString *)key;- (nullable id)valueForKeyPath:(NSString *)keyPath;
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
- (BOOL)validateValue:(inout id __nullable * __nonnull)ioValue forKeyPath:(NSString *)inKeyPath error:(out NSError **)outError;
- (NSMutableArray *)mutableArrayValueForKeyPath:(NSString *)keyPath;
- (NSMutableOrderedSet *)mutableOrderedSetValueForKeyPath:(NSString *)keyPath NS_AVAILABLE(10_7, 5_0);
- (NSMutableSet *)mutableSetValueForKeyPath:(NSString *)keyPath;- (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;
- (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *, id> *)keyedValues;
@end
@interface NSArray<ObjectType>(NSKeyValueCoding)
- (id)valueForKey:(NSString *)key;
- (void)setValue:(nullable id)value forKey:(NSString *)key;
@end
@interface NSDictionary<KeyType, ObjectType>(NSKeyValueCoding)
- (nullable ObjectType)valueForKey:(NSString *)key;
@end
@interface NSMutableDictionary<KeyType, ObjectType>(NSKeyValueCoding)
- (void)setValue:(nullable ObjectType)value forKey:(NSString *)key;
@end
@interface NSOrderedSet<ObjectType>(NSKeyValueCoding)
- (id)valueForKey:(NSString *)key NS_AVAILABLE(10_7, 5_0);- (void)setValue:(nullable id)value forKey:(NSString *)key NS_AVAILABLE(10_7, 5_0);
@end
@interface NSSet<ObjectType>(NSKeyValueCoding)
- (id)valueForKey:(NSString *)key;
- (void)setValue:(nullable id)value forKey:(NSString *)key;
@end
未完待续……
网友评论