KVC
KVC -- Key Value Coding 键值编码
- 键值编码的基本概念
- 键值编码是一个用于简介访问对象属性的机制,使用该机制不需要调用存取方法和变量实例就可以访问对象属性
- 键值编码的方法在object-c非正式协议(类目)NSKeyValueCoding中被声明,默认的实现方法有NSObject提供。
- 键值编码支持带有对象值得属性,同时也支持纯数值类型和结果。非对象参数和返回值类型会被识别并自动封装/解封。
- 设置和访问
键值编码中的基本调用包括-valueForKey: 和 -setValue:forKey:这两个方法,它们以字符串的形式向对象发送消息,字符串是我们关注属性的关键。
Person *person = [[Person alloc] init];
[person setValue:@"小明" forKey:@"name"];
NSString *personName = [person valueForKey:@"name"];
/*
[person setValue:@"小明" forKey:@"_name"];
NSString *personName = [person valueForKey:@"_name"];
*/
/*
KVC 首先会去调用对象的setter、getter方法,如果setter、getter方法不存在则会查找实例变量名为name的属性
如果实例变量名为name的属性不存在会查找实例变量名为_name的属性。如果都不存在会报错。
*/
打印结果:
personName = 小明
是否存在setter、getter方法,如果不存在,它将在内部查找名为_key 或 key的实例变量。通过KVC,可以获取不存在getter方法的对象值,无需通过对象指针直接访问。这里我们需要注意,当我们通过-setValue:forKey:设置对象的值,或通过-valueForKey: 来获取对象的值时,如若对象的实例变量为基本数据类型时(char、int、float、BOOL),我们需要对数据进行封装。
- 路径
除了通过键值设置外,键值编码还支持指定路径,像文件系统一样。[person setValue:@"小明" forKeyPath:@"name"]; NSString *personName = [person valueForKeyPath:@"name"];
先创建Person 和 Dog 这两个类
Person类
#import <Foundation/Foundation.h>
@class Dog;
@interface Person : NSObject
@property(nonatomic,copy) NSString *name;
@property(nonatomic,assign) int age;
@property(nonatomic,assign) float money;
@property (nonatomic, strong) Dog *dog;
- (void)printTelephone;
/**
* 通过KVC进行字典转模型
*
* @param dic 需要解析的字典
*/
- (void)dictionaryToModel:(NSDictionary *)dic;
@end
#import "Person.h"
#import "Dog.h"
@interface Person ()
// Person 私有属性
@property (nonatomic, strong) NSString *telephone;
@end
@implementation Person
/**
* 初始化方法
*
* @return <#return value description#>
*/
- (instancetype)init{
self = [super init];
if (self != nil) {
_telephone = @"18200002222";
};
return self;
}
- (void)printTelephone{
NSLog(@"电话为: %@",_telephone);
}
- (void)dictionaryToModel:(NSDictionary *)dic{
[self setValuesForKeysWithDictionary:dic];
}
@end
Dog类
#import <Foundation/Foundation.h>
@interface Dog : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) int price;
@end
#import "Dog.h"
@implementation Dog
@end
使用KVC给Person 和 Dog 的属性赋值
Person *person = [[Person alloc] init];
person.dog = [[Dog alloc] init];
[person setValue:@"小明" forKey:@"name"];
[person setValue:@"22" forKey:@"age"];
[person setValue:@"1000" forKeyPath:@"money"];
[person setValue:@"旺财" forKeyPath:@"dog.name"];
/*
这种方式也可以给dog的name赋值
[person.dog setValue:@"旺财" forKeyPath:@"name"];
*/
打印结果:
姓名: 小明
年龄: 22
金钱: 1000.00(传入的是一个字符串类型@"1000",说明KVC可以自动进行类型转换)
狗名: 旺财
- forKey 和 forKeyPath 的一些差异
- forKeyPath 包含了所有 forKey 的功能
- forKeyPath 可以进行内部的点语法,层层访问内部的属性
```
[person setValue:@"旺财" forKeyPath:@"dog.name"];
```
- key值一定要在属性中找到,否则会有crash
```
[person setValue:@"小明" forKey:@"name11"];
```
```
*** Terminating app due to uncaught exception 'NSUnknownKeyException',
reason: '[<Person 0x7ff28bd1ca80> setValue:forUndefinedKey:]:
this class is not key value coding-compliant for the key name11.'
```
可以通过KVC修改类的私有成员变量
Person *person = [[Person alloc] init];
[person printTelephone];
[person setValue:@"15066669999" forKey:@"telephone"];
[person printTelephone];
打印结果:
//初始化默认号码
电话为: 18200002222
//修改过之后的号码
电话为: 15066669999
使用KVC实现字典转模型
Person.h 中声明部分
/**
* 通过KVC进行字典转模型
*
* @param dic 需要解析的字典
*/
- (void)dictionaryToModel:(NSDictionary *)dic;
Person.m 中实现部分
- (void)dictionaryToModel:(NSDictionary *)dic{
[self setValuesForKeysWithDictionary:dic];
}
使用过程:
NSDictionary *dic = @{@"name":@"小王",
@"age":@"33",
@"money":@"1000",
@"dog":@{@"name":@"wang cai",
@"price":@"500"},
};
Person *person = [[Person alloc] init];
person.dog = [[Dog alloc] init];
[person dictionaryToModel:dic];
NSLog(@"姓名: %@",person.name);
NSLog(@"年龄: %d",person.age);
NSLog(@"金钱: %.2f",person.money);
NSLog(@"人所拥有的狗: %@",person.dog);
NSLog(@"狗的类型: %@",person.dog.class);
打印结果:
姓名: 小王
年龄: 33
金钱: 1000.00
人所拥有的狗: {
name = "wang cai";
price = 500;
}
狗的类型: __NSDictionaryI
如果此时要打印狗的信息:name 和 price 会报错
NSDictionary *dic = @{@"name":@"小王",
@"age":@"33",
@"money":@"1000",
@"dog":@{@"name":@"wang cai",
@"price":@"500"},
};
Person *person = [[Person alloc] init];
person.dog = [[Dog alloc] init];
[person dictionaryToModel:dic];
NSLog(@"姓名: %@",person.name);
NSLog(@"年龄: %d",person.age);
NSLog(@"金钱: %.2f",person.money);
NSLog(@"人所拥有的狗: %@",person.dog);
NSLog(@"狗的类型: %@",person.dog.class);
NSLog(@"狗的名字: %@",person.dog.name);
NSLog(@"狗的价格: %d",person.dog.price);
姓名: 小王
年龄: 33
金钱: 1000.00
人所拥有的狗: {
name = "wang cai";
price = 500;
}
狗的类型: __NSDictionaryI
-[__NSDictionaryI name]: unrecognized selector sent to instance 0x7fa461e0df20
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSDictionaryI name]: unrecognized selector sent to instance 0x7fa461e0df20'
*** First throw call stack:
(
0 CoreFoundation 0x000000010796ce65 __exceptionPreprocess + 165
1 libobjc.A.dylib 0x00000001073e5deb objc_exception_throw + 48
2 CoreFoundation 0x000000010797548d -[NSObject(NSObject) doesNotRecognizeSelector:] + 205
3 CoreFoundation 0x00000001078c290a ___forwarding___ + 970
4 CoreFoundation 0x00000001078c24b8 _CF_forwarding_prep_0 + 120
5 图片处理 0x0000000106ee4df1 -[ViewController test1] + 865
6 图片处理 0x0000000106ee4469 -[ViewController viewDidLoad] + 73
7 UIKit 0x0000000107eaff98 -[UIViewController loadViewIfRequired] + 1198
8 UIKit 0x0000000107eb02e7 -[UIViewController view] + 27
9 UIKit 0x0000000107d86ab0 -[UIWindow addRootViewControllerViewIfPossible] + 61
10 UIKit 0x0000000107d87199 -[UIWindow _setHidden:forced:] + 282
11 UIKit 0x0000000107d98c2e -[UIWindow makeKeyAndVisible] + 42
12 UIKit 0x0000000107d11663 -[UIApplication _callInitializationDelegatesForMainScene:transitionContext:] + 4131
13 UIKit 0x0000000107d17cc6 -[UIApplication _runWithMainScene:transitionContext:completion:] + 1760
14 UIKit 0x0000000107d14e7b -[UIApplication workspaceDidEndTransaction:] + 188
15 FrontBoardServices 0x000000010a6e5754 -[FBSSerialQueue _performNext] + 192
16 FrontBoardServices 0x000000010a6e5ac2 -[FBSSerialQueue _performNextFromRunLoopSource] + 45
17 CoreFoundation 0x0000000107898a31 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
18 CoreFoundation 0x000000010788e95c __CFRunLoopDoSources0 + 556
19 CoreFoundation 0x000000010788de13 __CFRunLoopRun + 867
20 CoreFoundation 0x000000010788d828 CFRunLoopRunSpecific + 488
21 UIKit 0x0000000107d147cd -[UIApplication _run] + 402
22 UIKit 0x0000000107d19610 UIApplicationMain + 171
23 图片处理 0x0000000106ee570f main + 111
24 libdyld.dylib 0x000000010a0a892d start + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
/*
KVC 赋值是非常暴力的
给person对象狗属性赋值的时候相当于
[person setValue:@{@"name":@"wang cai",
@"price":@"500"} forKey:@"dog"];
直接把@{@"name":@"wang cai",@"price":@"500"}赋值给dog属性
所以dog得class打印出来是__NSDictionaryI
开发中不介意使用setValuesForKeysWithDictionary:
1.字典中的key值必须在模型的属性中找到
2.如果模型中的属性带有模型,setValuesForKeysWithDictionary:不能正确转换
应用场景:简单的字典转模型 ----> 框架(MJExtention)
*/
KVC setValuesForKeysWithDictionary:底层实现,解决模型中的属性带有模型不能正确转换问题
- KVC setValuesForKeysWithDictionary:底层实现
Person.h 中声明部分
/**
* 通过KVC进行字典转模型
*
* @param dic 需要解析的字典
*/
- (void)dictionaryToModel:(NSDictionary *)dic;
Person.m 中实现部分
- (void)dictionaryToModel:(NSDictionary *)dic{
// [self setValuesForKeysWithDictionary:dic];
// KVC
// setValuesForKeysWithDictionary:底层实现
// 遍历字典中的所有key,去模型中查找有没有对应的属性名,如果就给这个属性赋值
[dic enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
[self setValue:obj forKey:key];
NSLog(@"%@ %@",key,obj);
/*
age 33
money 1000
dog {
name = "wang cai";
price = 500;
}
name 小王
*/
/*
以name属性为例:
[self setValue:@"小王" forKey:@"name"];
1.首先去模型中查找有么有setName方法,如果有就直接调用
[self setName:@"小王"];
2.没有setName方法,继续去模型中查找有没有name属性,如果有,就直接访问成员属性
name = @"小王";
3.如果没有name属性,继续去模型中查找有没有_name属性,如果有,就直接访问成员属性
_name = @"小王";
4.如果都找不到,就直接报错。
*/
}];
}
使用过程:
NSDictionary *dic = @{@"name":@"小王",
@"age":@"33",
@"money":@"1000",
@"dog":@{@"name":@"wang cai",
@"price":@"500"},
};
Person *person = [[Person alloc] init];
person.dog = [[Dog alloc] init];
[person dictionaryToModel:dic];
NSLog(@"姓名: %@",person.name);
NSLog(@"年龄: %d",person.age);
NSLog(@"金钱: %.2f",person.money);
NSLog(@"人所拥有的狗: %@",person.dog);
NSLog(@"狗的类型: %@",person.dog.class);
打印结果:
姓名: 小王
年龄: 33
金钱: 1000.00
人所拥有的狗: {
name = "wang cai";
price = 500;
}
狗的类型: __NSDictionaryI
- 解决模型中的属性带有模型不能正确转换问题
修改Person.m,添加dog的setter方法
#import "Person.h"
#import "Dog.h"
@interface Person ()
// Person 私有属性
@property (nonatomic, strong) NSString *telephone;
@end
@implementation Person
/**
* 初始化方法
*
* @return <#return value description#>
*/
- (instancetype)init{
self = [super init];
if (self != nil) {
_telephone = @"18200002222";
};
return self;
}
- (void)printTelephone{
NSLog(@"电话为: %@",_telephone);
}
- (void)dictionaryToModel:(NSDictionary *)dic{
[self setValuesForKeysWithDictionary:dic];
}
/**
* 修改dog的set方法,如果传入的是Dog类型就给dog属性执行属性赋值操作
* 如果传入的是dit字典类型就通setValuesForKeysWithDictionary:给Dog模型赋值
*
* @param dog <#dog description#>
*/
- (void)setDog:(id)dog{
if ([dog isKindOfClass:[Dog class]]) {
_dog = dog;
} else {
if (_dog != nil && [dog isKindOfClass:[NSDictionary class]]) {
[_dog setValuesForKeysWithDictionary:dog];
}
}
}
@end
使用过程:
NSDictionary *dic = @{@"name":@"小王",
@"age":@"33",
@"money":@"1000",
@"dog":@{@"name":@"wang cai",
@"price":@"500"},
};
Person *person = [[Person alloc] init];
person.dog = [[Dog alloc] init];
[person dictionaryToModel:dic];
NSLog(@"姓名: %@",person.name);
NSLog(@"年龄: %d",person.age);
NSLog(@"金钱: %.2f",person.money);
NSLog(@"人所拥有的狗: %@",person.dog);
NSLog(@"狗的类型: %@",person.dog.class);
NSLog(@"狗的名字: %@",person.dog.name);
NSLog(@"狗的价格: %d",person.dog.price);
打印结果:
姓名: 小王
年龄: 33
金钱: 1000.00
人所拥有的狗: <Dog: 0x7fddbad16700>
狗的类型: Dog
狗的名字: wang cai
狗的价格: 500
使用KVC实现模型转字典
Person *person = [[Person alloc] init];
person.dog = [[Dog alloc] init];
[person setValue:@"xiao ming" forKeyPath:@"name"];
[person setValue:@"22" forKey:@"age"];
[person setValue:@"1000" forKeyPath:@"money"];
[person setValue:@"旺财" forKeyPath:@"dog.name"];
NSDictionary *dic = [person dictionaryWithValuesForKeys:@[@"name",@"age",@"money",@"dog"]];
NSLog(@"dic = %@",dic);
打印结果:
dic = {
age = 22;
dog = "<Dog: 0x7feaa9703360>";
money = 1000;
name = "xiao ming";
}
使用KVC取出数组中所有模型的某个属性(数组中所有模型类型相同)
NSMutableArray *personArray = [[NSMutableArray alloc] initWithCapacity:10];
for (int i = 0; i < 10; i++) {
Person *person = [[Person alloc] init];
person.dog = [[Dog alloc] init];
NSString *name = [NSString stringWithFormat:@"xiao ming %d",i];
[person setValue:name forKeyPath:@"name"];
NSString *age = [NSString stringWithFormat:@"2%d",i];
[person setValue:age forKey:@"age"];
NSString *money = [NSString stringWithFormat:@"1%d00",i];
[person setValue:money forKeyPath:@"money"];
[personArray addObject:person];
}
NSArray *nameArray = [personArray valueForKeyPath:@"name"];
NSArray *ageArray = [personArray valueForKeyPath:@"age"];
NSArray *moneyArray = [personArray valueForKeyPath:@"money"];
NSLog(@"nameArray = %@",nameArray);
NSLog(@"ageArray = %@",ageArray);
NSLog(@"moneyArray = %@",moneyArray);
打印结果:
nameArray = (
"xiao ming 0",
"xiao ming 1",
"xiao ming 2",
"xiao ming 3",
"xiao ming 4",
"xiao ming 5",
"xiao ming 6",
"xiao ming 7",
"xiao ming 8",
"xiao ming 9"
)
ageArray = (
20,
21,
22,
23,
24,
25,
26,
27,
28,
29
)
moneyArray = (
1000,
1100,
1200,
1300,
1400,
1500,
1600,
1700,
1800,
1900
)
在KVC面前所以属性都是透明可操作的。