本文为L_Ares个人写作,以任何形式转载请表明原文出处。
网上可以搜索到的KVC
文章太多了,之所以都这么喜欢研究这个东西,是因为KVC
的作用域之广泛,只要你还需要用到对象这个概念,怕是都有可能要碰到KVC
帮你解决问题。
本节将从苹果官方文档来进入KVC
。不再使用源码是因为KVC
的源码在Foundation
框架中,找不到其开源的源码。

准备工作 : 苹果官方文档。必须要有这个。进入之后搜索栏自行搜索
key value coding
,不要搜索kvc
。这里直达KVC。
一、KVC基本简介
1. KVC的定义
-
英文全称 :
key value coding
(就是准备工作里面让大家搜的) -
中文全称 :
键值编码
-
主要功能 :
- 把
OC
的私有变量变成了单一名词 : 通过变量名称字符串
直接访问成员变量
,无论公有
还是私有
。 - 践行了
OC
的动态性 :无需调用
明确的存取方法
,动态的访问和修改
对象属性。
- 把
-
依赖性 : 根据
KVC
的主要功能就能知道,依赖的是Runtime
。
2. KVC的简介
下面这些都是从官方文档里面根据自身的一些了解翻译的,有不当之处,还请指出,感激不尽。
(1).
KVC
本身是一种机制,它是根据NSKeyValueCoding
的非正式协议
使用的。对象采用NSKeyValueCoding非正式协议
可以对其属性进行访问。
(2). 当一个对象兼容键值对编码(kvc)
时,可以通过简洁、统一的消息传递接口利用字符串参数
对它的属性进行访问。
(3).KVC
这种间接的访问机制,提供了一种直接访问实例变量和它们的settet
、getter
的方式。
我们经常使用setter
或者getter
来访问对象的属性。大家也清楚setter
可以给属性赋值,getter
可以返回属性的值。在OC
中,你还可以直接访问属性的底层的实例变量。
使用上述的任何一种方式访问对象的属性都很简单,但是都需要调用属性自己的,特定的方法或者变量名。而且随着属性列表的增加或者更改,访问属性的代码也需要增加或者更改。KVC
就提供了一种更简单的,更统一的消息传递接口,对所有遵循KVC
机制的属性都可以适用。
KVC
是其他很多Cocoa
技术的基础概念,比如说键值观察(KVO)
、Cocoa Binding
、CoreData
、AppleScript(写mac脚本的)
。在某些情况下,KVC
还可以帮我们简化一些代码。
到这里,大体对KVC
也算有一个官方的理解了。这个模块的一些其他内容就不翻译了,后面有时间再翻译一下,也可以去网上搜索,有很多的小伙伴也写过的,就不赘述了,下面直接上第二个模块的内容,用代码来直接实现官方的一些规定。
二、KVC中常见API
1. KVC设置值
1.1 key设置value
- (void)setValue:(nullable id)value forKey:(NSString *)key;
1.2 keyPath设置value
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
2. KVC取值
2.1 通过key取value
- (nullable id)valueForKey:(NSString *)key;
2.2 通过keyPath取value
- (nullable id)valueForKeyPath:(NSString *)keyPath;
3. 其他常见API
- 对实例变量而言(不对属性),开启或者关闭
实例变量
赋值。默认返回YES
,开启。
+ (BOOL)accessInstanceVariablesDirectly;
大家都知道,实例变量和属性不同,是不默认生成
setter
方法的,如果不开启这个,又不自己给实例变量添加set方法
的话,那么就不可以直接给实例变量进行赋值。
-
检查给
key
的value
是否是有效的。
也就是说,可以验证给指定的key
去set
的值是否正确。如果不正确,可以替换或者拒绝,并且可以给error
的地址上添加错误的原因。
- (BOOL)validateValue:(inout id __nullable * __nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;
- 属性是
NSMutableArray
类型,通过key
获取该属性。
- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;
-
无法识别的
key
key
不存在,且KVC
找不到任何与key
的字符串相关的字段或属性,会调用到这里,默认是抛出异常。
- (nullable id)valueForUndefinedKey:(NSString *)key;
-
对无法识别的
key
赋值
key
不存在,KVC
也找不到任何与key
的字符串相关的字段或属性,无法对key
进行设置value
,会调用到这里,默认抛出异常。
- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;
-
设置
key
的value
为nil
如果给一个已知的key
设置value = nil;
那么就会走到这里。
- (void)setNilValueForKey:(NSString *)key;
-
根据一组
key
找到value
然后转成NSDictionary
该方法可以用于模型转字典。
- (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;
三、一些简单的KVC使用
准备 : 创建一个
Project
--->App
。创建一个继承于NSObject
的JDPerson
类。再创建一个继承于NSObject
的JDMan
类。
JDPerson.h
:
#import <Foundation/Foundation.h>
#import "JDMan.h"
NS_ASSUME_NONNULL_BEGIN
typedef struct {
float x,y,z;
}ThreeFloats;
@interface JDPerson : NSObject
{
@public
NSString *myName;
}
@property (nonatomic, copy) NSString *name;
@property (nonatomic, strong) NSArray *array;
@property (nonatomic, strong) NSMutableArray *mutArr;
@property (nonatomic, assign) int age;
@property (nonatomic) ThreeFloats threeFloats;
@property (nonatomic, strong) JDMan *man;
@end
NS_ASSUME_NONNULL_END
JDMan.h
:
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface JDMan : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *work;
@property (nonatomic, copy) NSString *hobby;
@property (nonatomic, assign) int age;
@property (nonatomic, assign) int height;
@property (nonatomic, strong) NSMutableArray *bookArr;
@end
NS_ASSUME_NONNULL_END
在ViewController
中引入JDPerson.h
和JDMan.h
。准备工作完成。
KVC中常见的使用
1 基本类型设置值
#pragma mark - KVC基本类型赋值与取值
- (void)jd_kvc_basicType
{
/**
普通的setter方法对对象进行赋值
*/
JDPerson *person = [[JDPerson alloc]init];
person.name = @"JD";
person.age = 18;
person->myName = @"LJD";
NSLog(@"setter方法 : %@ - %d - %@",person.name,person.age,person->myName);
/**
1. KVC : 基本类型
*/
[person setValue:@"JD_KVC" forKey:@"name"];
[person setValue:@16 forKey:@"age"];
[person setValue:@"LJD_JVC" forKey:@"myName"];
NSLog(@"KVC : 基本类型 : %@ - %@ - %@",[person valueForKey:@"name"],
[person valueForKey:@"age"],
[person valueForKey:@"myName"]);
}
2 集合类型修改值
#pragma mark - KVC集合类型修改值
- (void)jd_kvc_collectionTypes
{
/**
2. KVC : 集合类型
修改集合中的第一个元素@"1"
由于array是不可变数组,不可以直接进行修改
*/
JDPerson *person = [[JDPerson alloc]init];
person.array = @[@"1",@"2",@"3"];
//普通KVC方式
NSArray *array = [person valueForKey:@"array"];
array = @[@"666",@"2",@"3"];
[person setValue:array forKey:@"array"];
NSLog(@"集合类型-普通KVC方式 : %@",[person valueForKey:@"array"]);
//KVC API方式
NSMutableArray *mutArr = [person mutableArrayValueForKey:@"array"];
mutArr[0] = @"888";
NSLog(@"集合类型-KVC API方式 : %@",[person valueForKey:@"array"]);
}
3 集合操作符
这里包含一些KVC
特殊的操作符,比如lowercaseString
(小写)、@avg
(平均数)、@count
(数量)、@sum
(求和)、@max
(最大值)、@min
(最小值)、@unionOfObjects
(相同的key的value)、@distinctUnionOfObjects
(相同的key的value并且去重)等等。
直接上代码 :
#pragma mark - KVC字典操作
- (void)jd_kvc_dictionary
{
NSDictionary *dic = @{
@"name" : @"ljd",
@"work" : @"coder",
@"age" : @18,
@"height" : @150,
@"hobby" : @"study"
};
JDMan *man = [[JDMan alloc] init];
//普通的字典转模型
[man setValuesForKeysWithDictionary:dic];
NSLog(@"KVC字典操作-字典转模型 : %@-%@-%d-%d-%@",man.name,man.work,man.age,man.height,man.hobby);
//通过key的数组转模型到字典
NSArray *keyArray = @[@"name",@"work"];
NSDictionary *dict = [man dictionaryWithValuesForKeys:keyArray];
NSLog(@"KVC字典操作-通过key的数组转模型到字典 : %@",dict);
}
#pragma mark - KVC消息传递
- (void)jd_kvc_messagePass
{
//消息从tempArray传递到了string
NSArray *tempArray = @[@"Apple",@"Banana",@"Grapes",@"Peach"];
NSArray *lengthArr = [tempArray valueForKeyPath:@"length"];
NSLog(@"KVC消息传递-length : %@",lengthArr);
NSArray *lowStrArr = [tempArray valueForKeyPath:@"lowercaseString"];
NSLog(@"KVC消息传递-小写 : %@",lowStrArr);
}
#pragma mark - KVC聚合操作符
- (void)jd_kvc_aggregation_operator
{
NSMutableArray *manArr = [NSMutableArray array];
for (int i = 0; i < 6; i++) {
JDMan *man = [JDMan new];
NSDictionary *dic = @{
@"name" : @"JD",
@"work" : @"coder",
@"hobby" : @"study",
@"age" : @(18 + i),
@"height": @(175 + 2 * arc4random_uniform(6))
};
[man setValuesForKeysWithDictionary:dic];
[manArr addObject:man];
}
//取对象中的`height`,结果是一个数组
NSLog(@"KVC聚合操作符-height : %@",[manArr valueForKey:@"height"]);
//取`height`平均数
float avg_length = [[manArr valueForKeyPath:@"@avg.height"] floatValue];
NSLog(@"KVC聚合操作符-平均数 : %f",avg_length);
//`height`的数量
int count = [[manArr valueForKeyPath:@"@count.height"] intValue];
NSLog(@"KVC聚合操作符-属性数量 : %d",count);
//求和
int sum = [[manArr valueForKeyPath:@"@sum.height"] intValue];
NSLog(@"KVC聚合操作符-求和 : %d",sum);
//最大值
int max = [[manArr valueForKeyPath:@"@max.height"] intValue];
NSLog(@"KVC聚合操作符-最大值 : %d",max);
//最小值
int min = [[manArr valueForKeyPath:@"@min.height"] intValue];
NSLog(@"KVC聚合操作符-最小值 : %d",min);
}
#pragma mark - KVC数组操作符
- (void)jd_kvc_array_operator
{
NSMutableArray *manArr = [NSMutableArray array];
for (int i = 0; i < 6; i++) {
JDMan *man = [JDMan new];
NSDictionary *dic = @{
@"name" : @"JD",
@"work" : @"coder",
@"hobby" : @"study",
@"age" : @(18 + i),
@"height": @(175 + 2 * arc4random_uniform(6))
};
[man setValuesForKeysWithDictionary:dic];
[manArr addObject:man];
}
//取数组中的对象元素的某一属性,然后形成对象属性数组
//通过key取
NSLog(@"KVC数组操作符-key取属性 : %@",[manArr valueForKey:@"height"]);
//通过keyPath取
NSLog(@"KVC数组操作符-keyPath取属性 : %@",[manArr valueForKeyPath:@"@unionOfObjects.height"]);
//通过keyPath取,并且属性去重
NSLog(@"KVC数组操作符-keyPath取属性且去重 : %@",[manArr valueForKeyPath:@"@distinctUnionOfObjects.height"]);
}
#pragma mark - KVC嵌套数组操作符
- (void)jd_kvc_nested_array_operator
{
NSMutableArray *manArr = [NSMutableArray array];
for (int i = 0; i < 6; i++) {
JDMan *man = [JDMan new];
NSDictionary *dic = @{
@"name" : @"JD",
@"work" : @"coder",
@"hobby" : @"study",
@"age" : @(18 + arc4random_uniform(6)),
@"height": @(175 + i)
};
[man setValuesForKeysWithDictionary:dic];
[manArr addObject:man];
}
NSMutableArray *personArr = [NSMutableArray array];
for (int i = 0; i < 6; i++) {
JDPerson *person = [JDPerson new];
NSDictionary *dic = @{
@"name" : @"JD",
@"age" : @(18 + arc4random_uniform(6)),
};
[person setValuesForKeysWithDictionary:dic];
[personArr addObject:person];
}
//嵌套数组
NSArray *nestArr = @[manArr,personArr];
//取嵌套数组的两个数组中对象元素的某一相同属性,然后形成对象属性数组
NSLog(@"KVC嵌套数组操作符 - %@",[nestArr valueForKeyPath:@"@unionOfArrays.age"]);
//去重
NSLog(@"KVC嵌套数组操作符-去重 - %@",[nestArr valueForKeyPath:@"@distinctUnionOfArrays.age"]);
}
#pragma mark - KVC嵌套集合
- (void)jd_kvc_nested_set
{
NSMutableSet *manSet = [NSMutableSet set];
for (int i = 0; i < 6; i++) {
JDMan *man = [JDMan new];
NSDictionary *dic = @{
@"name" : @"JD",
@"work" : @"coder",
@"hobby" : @"study",
@"age" : @(18 + arc4random_uniform(6)),
@"height": @(175 + i)
};
[man setValuesForKeysWithDictionary:dic];
[manSet addObject:man];
}
NSMutableSet *personSet = [NSMutableSet set];
for (int i = 0; i < 6; i++) {
JDPerson *person = [JDPerson new];
NSDictionary *dic = @{
@"name" : @"JD",
@"age" : @(18 + arc4random_uniform(6)),
};
[person setValuesForKeysWithDictionary:dic];
[personSet addObject:person];
}
//嵌套Set
NSSet *nestSet = [NSSet setWithObjects:manSet, personSet, nil];
NSLog(@"KVC嵌套集合-交集 - %@",[nestSet valueForKeyPath:@"@distinctUnionOfSets.age"]);
}
4 访问非对象属性
非对象属性,就像那些常见的基本类型,还有结构体之类的。这里主要说一下结构体。因为有的人会利用这种方法做C/C++
和OC
的混编。
这种基本数据类型或者数据结构是不可以直接当value
使用的,要根据其类型转换成NSValue
或者其他的OC
类。
#pragma mark - KVC访问非对象属性
- (void)jd_kvc_non_object_attribute
{
JDPerson *person = [[JDPerson alloc] init];
ThreeFloats tfValue = {1.f,2.f,3.f};
NSValue *value = [NSValue valueWithBytes:&tfValue objCType:@encode(ThreeFloats)];
[person setValue:value forKey:@"threeFloats"];
NSValue *resultValue = [person valueForKey:@"threeFloats"];
NSLog(@"KVC访问非对象属性-结构体 - %@",resultValue);
ThreeFloats three_value;
[resultValue getValue:&three_value];
NSLog(@"KVC访问非对象属性-打印 - %f-%f-%f",three_value.x,three_value.y,three_value.z);
}
5 层层访问或者说对象嵌套
就是说对象的属性还是个对象。通过keyPath
就可以了。
#pragma mark - KVC 对象中的对象
- (void)jd_kvc_obj_include_obj
{
JDPerson *person = [[JDPerson alloc] init];
JDMan *man = [[JDMan alloc] init];
man.name = @"LJD";
person.man = man;
NSLog(@"KVC 对象中的对象-未改变 - %@",person.man.name);
//利用kvc的keyPath再给person的man中的name赋值
[person setValue:@"JD" forKeyPath:@"man.name"];
NSLog(@"KVC 对象中的对象-改变后 - %@---%@",[person valueForKeyPath:@"man.name"],man.name);
}
网友评论