一、概述
探究苹果某个api时候我们首先先从苹果的官方文档中查看该api的描述,这种方法叫做文档学习法。
这篇我看探究下KVC原理,首先我们进到官网上看看官方文档对KVC的描述KVC官方文档,根据文档描述
- KVC是根据非正式协议间接访问对象成员变量的一种方法
并且文档中有相应api的介绍以及例子
二、用法
下面我们来看看KVC的一些用法:
1、基本用法
直接对属性或者成员变量进行取值和赋值
LGPerson *person = [[LGPerson alloc] init];
[person setValue:@"KC" forKey:@"name"];
[person setValue:@19 forKey:@"age"];
[person setValue:@"酷C" forKey:@"myName"];
NSLog(@"%@ - %@ - %@",[person valueForKey:@"name"],[person valueForKey:@"age"],[person valueForKey:@"myName"]);
2、操作集合类型
针对集合属性,可以直接通过mutableArrayValueForKey对对象的集合属性进行操作更改
person.array = @[@"1",@"2",@"3"];
// KVC 的方式
NSMutableArray *ma = [person mutableArrayValueForKey:@"array"];
ma[0] = @"100";
NSLog(@"%@",[person valueForKey:@"array"]);
3、访问非对象属性
针对结构体,KVC也可以直接操作,但是操作时候需要将结构体转成NSValue类型,
typedef struct {
float x, y, z;
} ThreeFloats;
ThreeFloats floats = {1., 2., 3.};
NSValue *value = [NSValue valueWithBytes:&floats objCType:@encode(ThreeFloats)];
[person setValue:value forKey:@"threeFloats"];
NSValue *reslut = [person valueForKey:@"threeFloats"];
NSLog(@"%@",reslut);
ThreeFloats th;
[reslut getValue:&th] ;
NSLog(@"%f - %f - %f",th.x,th.y,th.z);
4、层层访问
假如对象的属性也是对象,那么KVC可以通过keyPath来操作对象属性的属性
LGStudent *student = [[LGStudent alloc] init];
student.subject = @"iOS";
person.student = student;
[person setValue:@"大师班" forKeyPath:@"student.subject"];
NSLog(@"%@",[person valueForKeyPath:@"student.subject"]);
5、集合操作符
(1)字典操作
假如字典的key和一个对象的属性都一样,那么可以通过setValuesForKeysWithDictionary直接将字典的value赋值给对象相应的属性,同样,也可以通过dictionaryWithValuesForKeys将对象转换成字典
- (void)dictionaryTest{
NSDictionary* dict = @{
@"name":@"Cooci",
@"nick":@"KC",
@"subject":@"iOS",
@"age":@18,
@"length":@180
};
LGStudent *p = [[LGStudent alloc] init];
// 字典转模型
[p setValuesForKeysWithDictionary:dict];
NSLog(@"%@:%@",p,p.name);
// 键数组转模型到字典
NSArray *array = @[@"name",@"age"];
NSDictionary *dic = [p dictionaryWithValuesForKeys:array];
NSLog(@"%@",dic);
}
(2)操作数组元素的信息(KVC消息传递)
通过api可以拿到数组元素的长度,也可以对数组元素进行操作得到新的数组
NSArray *array = @[@"Hank",@"Cooci",@"Kody",@"CC"];
//得到数组中所有元素的长度
NSArray *lenStr= [array valueForKeyPath:@"length"];
NSLog(@"%@",lenStr);
//将数组中所有值全变成小写
NSArray *lowStr= [array valueForKeyPath:@"lowercaseString"];
NSLog(@"%@",lowStr);
//将数组中所有值全变成大写
NSArray *uppercaseStr= [array valueForKeyPath:@"uppercaseString"];
NSLog(@"%@",uppercaseStr);
(3)、聚合操作符
@avg:取平均值 @count:取个数 @max:取最大值 @min:取最小值 @sum:求和
- (void)aggregationOperator{
NSMutableArray *personArray = [NSMutableArray array];
for (int i = 0; i < 6; i++) {
LGStudent *p = [LGStudent new];
NSDictionary* dict = @{
@"name":@"Tom",
@"age":@(18+i),
@"nick":@"Cat",
@"length":@(175 + 2*arc4random_uniform(6)),
};
[p setValuesForKeysWithDictionary:dict];
[personArray addObject:p];
}
NSLog(@"%@", [personArray valueForKey:@"length"]);
/// 平均身高
float avg = [[personArray valueForKeyPath:@"@avg.length"] floatValue];
NSLog(@"%f", avg);
int count = [[personArray valueForKeyPath:@"@count.length"] intValue];
NSLog(@"%d", count);
int sum = [[personArray valueForKeyPath:@"@sum.length"] intValue];
NSLog(@"%d", sum);
int max = [[personArray valueForKeyPath:@"@max.length"] intValue];
NSLog(@"%d", max);
int min = [[personArray valueForKeyPath:@"@min.length"] intValue];
NSLog(@"%d", min);
}
(4)、数组操作符
可以做去重等操作
- (void)arrayOperator{
NSMutableArray *personArray = [NSMutableArray array];
for (int i = 0; i < 6; i++) {
LGStudent *p = [LGStudent new];
NSDictionary* dict = @{
@"name":@"Tom",
@"age":@(18+i),
@"nick":@"Cat",
@"length":@(175 + 2*arc4random_uniform(6)),
};
[p setValuesForKeysWithDictionary:dict];
[personArray addObject:p];
}
NSLog(@"%@", [personArray valueForKey:@"length"]);
// 返回操作对象指定属性的集合
NSArray* arr1 = [personArray valueForKeyPath:@"@unionOfObjects.nick"];
NSLog(@"arr1 = %@", arr1);
// 返回操作对象指定属性的集合 -- 去重
NSArray* arr2 = [personArray valueForKeyPath:@"@distinctUnionOfObjects.length"];
NSLog(@"arr2 = %@", arr2);
}
5、嵌套操作
@distinctUnionOfArrays:去重取值 @unionOfArrays:取值
- (void)arrayNesting{
NSMutableArray *personArray1 = [NSMutableArray array];
for (int i = 0; i < 6; i++) {
LGStudent *student = [LGStudent new];
NSDictionary* dict = @{
@"name":@"Tom",
@"age":@(18+i),
@"nick":@"Cat",
@"length":@(175 + 2*arc4random_uniform(6)),
};
[student setValuesForKeysWithDictionary:dict];
[personArray1 addObject:student];
}
NSMutableArray *personArray2 = [NSMutableArray array];
for (int i = 0; i < 6; i++) {
LGPerson *person = [LGPerson new];
NSDictionary* dict = @{
@"name":@"Tom",
@"age":@(18+i),
@"nick":@"Cat",
@"length":@(175 + 2*arc4random_uniform(6)),
};
[person setValuesForKeysWithDictionary:dict];
[personArray2 addObject:person];
}
// 嵌套数组
NSArray* nestArr = @[personArray1, personArray2];
NSArray* arr = [nestArr valueForKeyPath:@"@distinctUnionOfArrays.length"];
NSLog(@"arr = %@", arr);
NSArray* arr1 = [nestArr valueForKeyPath:@"@unionOfArrays.length"];
NSLog(@"arr1 = %@", arr1);
}
(6)、嵌套操作二
- (void)setNesting{
NSMutableSet *personSet1 = [NSMutableSet set];
for (int i = 0; i < 6; i++) {
LGStudent *person = [LGStudent new];
NSDictionary* dict = @{
@"name":@"Tom",
@"age":@(18+i),
@"nick":@"Cat",
@"length":@(175 + 2*arc4random_uniform(6)),
};
[person setValuesForKeysWithDictionary:dict];
[personSet1 addObject:person];
}
NSLog(@"personSet1 = %@", [personSet1 valueForKey:@"length"]);
NSMutableSet *personSet2 = [NSMutableSet set];
for (int i = 0; i < 6; i++) {
LGPerson *person = [LGPerson new];
NSDictionary* dict = @{
@"name":@"Tom",
@"age":@(18+i),
@"nick":@"Cat",
@"length":@(175 + 2*arc4random_uniform(6)),
};
[person setValuesForKeysWithDictionary:dict];
[personSet2 addObject:person];
}
NSLog(@"personSet2 = %@", [personSet2 valueForKey:@"length"]);
// 嵌套set
NSSet* nestSet = [NSSet setWithObjects:personSet1, personSet2, nil];
// 交集
NSArray* arr1 = [nestSet valueForKeyPath:@"@distinctUnionOfSets.length"];
NSLog(@"arr1 = %@", arr1);
}
三、KVC取值和赋值的过程
1、赋值
a、官方解释
我们在官方文档上对kvc的赋值方法有下面解释
- 1、在调用setValue:forKey时候,系统会先去找set:和_set执行
- 2、如果没找到,那么再判断accessInstanceVariablesDirectly是不是返回YES,如果是,那么就找_, _is, , or is这样的成员变量进行赋值。
- 3、如果都没找到,那么就抛出setValue:forUndefinedKey:的异常
2、取值
我们看看官方文档对KVC的取值方法的描述
- 1、先找 get, , is, or _执行
- 2、如果属性是NSArray类型,并且对数组属性执行的是objectAtIndex方法,那么就就找objectInAtIndex:方法
- 3、如果没有,那么再判断accessInstanceVariablesDirectly是不是返回YES,如果是YES,则去找_, _is, , or is成员变量
- 4、如果成员变量没有,则抛出valueForUndefinedKey:.错误
3、验证
首先我们创建一个类,因为属性会生成set和get方法,会干扰我们验证,所以我们创建几个成员变量
@interface LGPerson : NSObject{
@public
NSString *_name;
NSString *_isName;
NSString *name;
NSString *isName;
}
@property (nonatomic, strong) NSArray *arr;
@end
@implementation LGPerson
#pragma mark - 关闭或开启实例变量赋值
+ (BOOL)accessInstanceVariablesDirectly{
return YES;
}
//MARK: - setKey. 的流程分析
//- (void)setName:(NSString *)name{
// NSLog(@"%s - %@",__func__,name);
//}
//- (void)_setName:(NSString *)name{
// NSLog(@"%s - %@",__func__,name);
//}
//- (void)setIsName:(NSString *)name{
// NSLog(@"%s - %@",__func__,name);
//}
//MARK: - valueForKey 流程分析 - get<Key>, <key>, is<Key>, or _<key>,
//- (NSString *)getName{
// return NSStringFromSelector(_cmd);
//}
//- (NSString *)name{
// return NSStringFromSelector(_cmd);
//}
//- (NSString *)isName{
// return NSStringFromSelector(_cmd);
//}
//- (NSString *)_name{
// return NSStringFromSelector(_cmd);
//}
@end
然后有下面代码测试:
LGPerson *person = [[LGPerson alloc] init];
// 1: KVC - 设置值的过程
[person setValue:@"LG_Cooci" forKey:@"name"];
NSLog(@"%@-%@-%@-%@",person->_name,person->_isName,person->name,person->isName);
NSLog(@"%@-%@-%@",person->_isName,person->name,person->isName);
NSLog(@"%@-%@",person->name,person->isName);
NSLog(@"%@",person->isName);
// 2: KVC - 取值的过程
person->_name = @"_name";
person->_isName = @"_isName";
person->name = @"name";
person->isName = @"isName";
NSLog(@"取值:%@",[person valueForKey:@"name"]);
四、KVC的异常小技巧
1、KVC能自动转换类型
KVC能自动把字符串类型的value转换成对应的int、bool等类型
LGPerson *person = [[LGPerson alloc] init];
[person setValue:@18 forKey:@"age"];
// 上面那个表达 大家应该都会! 但是下面这样操作也可以
[person setValue:@"20" forKey:@"age"]; // int - string
NSLog(@"%@-%@",[person valueForKey:@"age"],[[person valueForKey:@"age"] class]);//__NSCFNumber
[person setValue:@"20" forKey:@"sex"];
NSLog(@"%@-%@",[person valueForKey:@"sex"],[[person valueForKey:@"sex"] class]);//__NSCFNumber
其中LGPerson的age是int类型,set是bool类型 打印的内容:
2020-02-18 19:19:38.651192+0800 004-KVC异常小技巧[96796:3007843] 20-__NSCFNumber
2020-02-18 19:19:38.651351+0800 004-KVC异常小技巧[96796:3007843] 1-__NSCFBoolean
同样,对结构体也有用
2、设置空值
当对NSNumber和NSValue类型的成员变量赋值为nil时候会崩溃,也就是会对int、BOOL、float等类型的成员变量
[person setValue:nil forKey:@"age"]; // subject不会走 - 官方注释里面说只对 NSNumber - NSValue
解决办法是,写个分类,重写setNilValueForKey方法即可
- (void)setNilValueForKey:(NSString *)key{
NSLog(@"你傻不傻: 设置 %@ 是空值",key);
}
对了非NSNumber为啥不会崩溃呢,因为对于非NSNumber,nil本身就是其中的一个值
3、赋值时候找不到的 key
当对不存在的key进行赋值时候也会崩溃,解决办法时候就是在分类中重写下面方法即可:
- (void)setValue:(id)value forUndefinedKey:(NSString *)key{
NSLog(@"你瞎啊: %@ 没有这个key",key);
}
4、取值时候找不到key
对不存在的key进行取值时候也会崩溃,解决办法就是在分类中重写下面方法:
- (id)valueForUndefinedKey:(NSString *)key{
NSLog(@"你瞎啊: %@ 没有这个key - 给你一个其他的吧,别奔溃了!",key);
return @"Master 牛逼";
}
5、键值验证
当我们不知道对象是否有某个属性时候我们可以用validateValue方法先验证一下,然后再对属性进行操作
NSError *error;
NSString *name = @"LG_Cooci";
if (![person validateValue:&name forKey:@"names" error:&error]) {
NSLog(@"%@",error);
}else{
NSLog(@"%@",[person valueForKey:@"name"]);
}
或者在分类中重写该方法,同时也可以通过该方法进行赋值转发,将value给其他属性
- (BOOL)validateValue:(inout id _Nullable __autoreleasing *)ioValue forKey:(NSString *)inKey error:(out NSError *__autoreleasing _Nullable *)outError{
if([inKey isEqualToString:@"names"]){
[self setValue:[NSString stringWithFormat:@"里面修改一下: %@",*ioValue] forKey:@"name"];
return YES;
}
*outError = [[NSError alloc]initWithDomain:[NSString stringWithFormat:@"%@ 不是 %@ 的属性",inKey,self] code:10088 userInfo:nil];
return NO;
}