1.基本用法
直接对属性或者成员变量进行取值和赋值
GTPerson *person = [[GTPerson alloc] init];
[person setValue:@"GT" forKey:@"name"];
[person setValue:@29 forKey:@"age"];
[person setValue:@"GT" 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. 访问非对象属性
3.1 标量属性
标量属性.png如图所示,常用的基本数据类型需要在设置属性的时候包装成
NSNumber
类型,然后在读取值的时候使用各自对应的读取方法,如double
类型的标量读取的时候使用doubleValue
;
3.2 结构体
结构体.png针对结构体,KVC也可以直接操作,但是操作时候需要将结构体转成
NSValue
类型;结构体的话就需要转换成NSValue
类型,如上图所示。 除了NSPoint, NSRange, NSRect, 和 NSSize
,对于自定义的结构体
,也需要进行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来操作对象属性的属性
GTStudent *student = [[GTStudent alloc] init];
student.subject = @"iOSGG";
person.student = student;
[person setValue:@"GTOS" forKeyPath:@"student.subject"];
NSLog(@"%@",[person valueForKeyPath:@"student.subject"]);
5.集合操作符
5.1字典操作
假如字典的key和一个对象的属性都一样,那么可以通过
setValuesForKeysWithDictionary
直接将字典的value
赋值给对象相应的属性,同样,也可以通过dictionaryWithValuesForKeys
将对象转换成字典
- (void)dictionaryTest{
NSDictionary* dict = @{
@"name":@"Cooci",
@"nick":@"GitArtOS",
@"subject":@"iOSGG",
@"age":@29,
@"length":@120
};
GTStudent *p = [[GTStudent alloc] init];
// 字典转模型
[p setValuesForKeysWithDictionary:dict];
NSLog(@"%@:%@",p,p.name);
// 键数组转模型到字典
NSArray *array = @[@"name",@"age"];
NSDictionary *dic = [p dictionaryWithValuesForKeys:array];
NSLog(@"%@",dic);
}
5.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);
5.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":@"GitArtOS",
@"age":@(28+i),
@"nick":@"GTOS",
@"length":@(115 + 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);
}
5.4 数组操作符
可以做去做相应的
去重
等操作
- (void)arrayOperator{
NSMutableArray *personArray = [NSMutableArray array];
for (int i = 0; i < 6; i++) {
GTStudent *p = [GTStudent new];
NSDictionary* dict = @{
@"name":@"GitArtOS",
@"age":@(28+i),
@"nick":@"GTOS",
@"length":@(115 + 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.5 嵌套操作
@distinctUnionOfArrays:
去重取值@unionOfArrays:
取值
- (void)arrayNesting{
NSMutableArray *personArray1 = [NSMutableArray array];
for (int i = 0; i < 6; i++) {
GTStudent *student = [GTStudent new];
NSDictionary* dict = @{
@"name":@"GitArtOS",
@"age":@(28+i),
@"nick":@"GTOS",
@"length":@(115 + 2*arc4random_uniform(6)),
};
[student setValuesForKeysWithDictionary:dict];
[personArray1 addObject:student];
}
NSMutableArray *personArray2 = [NSMutableArray array];
for (int i = 0; i < 6; i++) {
GTPerson *person = [GTPerson new];
NSDictionary* dict = @{
@"name":@"GitArtOS",
@"age":@(28+i),
@"nick":@"GTOS",
@"length":@(115 + 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);
}
5.6 嵌套操作另一种
- (void)setNesting{
NSMutableSet *personSet1 = [NSMutableSet set];
for (int i = 0; i < 6; i++) {
GTStudent *person = [GTStudent new];
NSDictionary* dict = @{
@"name":@"GitArtOS",
@"age":@(28+i),
@"nick":@"GTOS",
@"length":@(115 + 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++) {
GTStudent *person = [GTStudent new];
NSDictionary* dict = @{
@"name":@"GitArtOS",
@"age":@(28+i),
@"nick":@"GTOS",
@"length":@(115 + 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);
}
6. 属性验证
KVC 支持属性验证,而这一特性是通过
validateValue:forKey:error:
(或validateValue:forKeyPath:error:
) 方法来实现的。这个验证方法的默认实现是去收到这个验证消息的对象(或keyPath中最后的对象)中根据 key 查找是否有对应的validate<Key>:error:
方法实现,如果没有,验证默认成功,返回 YES。
而由于validate<Key>:error:
方法通过引用接收值和错误参数,
所以会有以下三种结果:
- 验证成功,返回
YES
,对属性值不做任何改动。 - 验证失败,返回
NO
,但对属性值不做改动,如果调用者提供了NSError
的话,就把错误引用设置为指示错误原因的NSError
对象。 - 验证失败,返回
YES
,创建一个新的,有效的属性值作为替代。在返回之前,该方法将值引用修改为指向新值对象。 进行修改时,即使值对象是可变的,该方法也总是创建一个新对象,而不是修改旧对象。
GTPerson* person = [[GTPerson alloc] init];
NSError* error;
NSString* name = @"John";
if (![person validateValue:&name forKey:@"name" error:&error]) {
NSLog(@"%@",error);
}
那么是否系统会自动进行属性验证呢?
通常,KVC 或其默认实现均未定义任何机制来自动的执行属性验证,也就是说需要在适合你的应用的时候自己提供属性验证方法。
某些其他Cocoa
技术在某些情况下会自动执行验证。 例如,保存 managed object context
时,Core Data
会自动执行验证。另外,在 macOS
中,Cocoa Binding
允许你指定验证应自动进行。
7 KVC使用的注意事项
在实际过程中, 一个类的成员变量有可能是自定义的类或者其他的复杂类型, 这时候如果想要使用KVC获取到自定义类的属性就会比较麻烦。这时KVC给我们提供了一个解决方案, 键路径
keyPath
。方法如下:
- (nullable id)valueForKeyPath:(NSString *)keyPath; //通过KeyPath来取值
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath; //通过KeyPath来设值
验证代码
//
GTStudent *student = [[GTStudent alloc] init];
student.classNumber = @"num1";
student.number = @"029";
person.student = student;
NSLog(@"classNum: %@", [personvalueForKeyPath:@"student.classNumber"]);
// 打印结果
2020-03-01 23:27:08.482381+0800 GTtest[96366:1435734] classNum: num1
上面展示keyPath
的简单用法, 此时如果我们调用的方法是 valueForKey:
的话, 一般情况下系统会去调用undefinedKey方法
, 因为没有找到这个属性及其相关的方法和实例变量。KVC在此方法中的搜索机制首先根据" . "
来分割key
, 然后在去按照上面的顺序去搜索下去。
8. KVC 异常处理
8.1 设置空值 nil
处理
我们知道KVC有时候会帮我们去自动转换我们所传的值, 但是当我们传nil
的时候KVC是怎么处理的呢?
- (void)setNilValueForKey:(NSString *)key;
- (void)setNilValueForKey:(NSString *)key{
NSLog(@"GT: 设置 %@ 是空值",key);//重写这个方法做nil处理
}
@property (nonatomic, copy) NSString *subject;
@property (nonatomic, assign) int age;
@property (nonatomic, assign) BOOL sex;
@property (nonatomic) ThreeFloats threeFloats;
// 测试代码
NSLog(@"******2: 设置空值******");
[person setValue:nil forKey:@"age"];
[person setValue:nil forKey:@"subject"];
[person setValue:nil forKey:@"sex"];
[person setValue:nil forKey:@"threeFloats"];
// 打印结果
2020-03-01 23:38:44.538117+0800 GTtest[3703:1577245] ******2: 设置空值******
2020-03-01 23:38:44:44.538200+0800 GTtest[3703:1577245] GT: 设置 age 是空值
2020-03-01 23:38:44:44.538293+0800 GTtest[3703:1577245] GT: 设置 sex 是空值
2020-02-16 16:58:44.538487+0800 GTtest[3703:1577245] GT: 设置 threeFloats 是空值
8.2 赋值时候找不到的 key
当对不存在的
key
进行赋值时候也会崩溃,解决办法时候就是在分类中重写下面方法即可:
- (void)setValue:(id)value forUndefinedKey:(NSString *)key{
NSLog(@"GT: %@ 没有这key",key);
}
8.3 取值时候找不到key
对不存在的key进行取值时候也会崩溃,解决办法就是在分类中重写下面方法:
- (id)valueForUndefinedKey:(NSString *)key{
NSLog(@"GT: %@ 没有这key - 给你一个其他的吧,别崩溃了!",key);
return @"NND 牛X";
}
KVC探讨-设定值 setValue: forKey:和取值valueForKey:(一)
KVC探讨-操作数组和集合、字典探讨(二)
KVC探讨-自定义KVC(四)
网友评论