主要内容:
KVC的使用
KVC的底层原理
自定义KVC的实现
KVC的全称是Key-Value Coding,即键值编码,依赖于NSKeyValueCoding协议,是非正式协议,所以所有的类都可以进行键值编码,它的作用就是通过字符串来访问其属性,这种间接访问机制补充了实例变量及其相关的访问器方法所提供的直接访问。
常用API
- 通过key 设值/取值
//直接通过Key来取值
- (nullable id)valueForKey:(NSString *)key;
//通过Key来设值
- (void)setValue:(nullable id)value forKey:(NSString *)key;
- 通过keyPath (即路由)设值/取值
//通过KeyPath来取值
- (nullable id)valueForKeyPath:(NSString *)keyPath;
//通过KeyPath来设值
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
- 其他方法
//默认返回YES,表示如果没有找到Set<Key>方法的话,会按照_key,_iskey,key,iskey的顺序搜索成员,设置成NO就不这样搜索
+ (BOOL)accessInstanceVariablesDirectly;
//KVC提供属性值正确性验证的API,它可以用来检查set的值是否正确、为不正确的值做一个替换值或者拒绝设置新值并返回错误原因。
- (BOOL)validateValue:(inout id __nullable * __nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;
//这是集合操作的API,里面还有一系列这样的API,如果属性是一个NSMutableArray,那么可以用这个方法来返回。
- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;
//如果Key不存在,且KVC无法搜索到任何和Key有关的字段或者属性,则会调用这个方法,默认是抛出异常。
- (nullable id)valueForUndefinedKey:(NSString *)key;
//和上一个方法一样,但这个方法是设值。
- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;
//如果你在SetValue方法时面给Value传nil,则会调用这个方法
- (void)setNilValueForKey:(NSString *)key;
//输入一组key,返回该组key对应的Value,再转成字典返回,用于将Model转到字典。
- (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;
代码实现
key设值取值
- (void)propertyTest{
WYPerson *person = [[WYPerson alloc] init];
[person setValue:@"zwy" forKey:@"name"];
NSLog(@"person的name为:%@",[person valueForKey:@"name"]);
}
keyPath设值取值
- (void)keyPathTest{
WYPerson *person = [[WYPerson alloc] init];
WYStudent *student = [[WYStudent alloc] init];
person.student = student;
[person setValue:@"studentWY" forKeyPath:@"student.name"];
NSLog(@"person下的student的name为:%@",[person valueForKeyPath:@"student.name"]);
}
结构体设值取值
-(void)structTest{
//非对象属性,需要包装成对象再使用
WYPerson *person = [[WYPerson alloc] init];
ThreeFloats floats = {1.,2.,3.};
NSValue *value = [NSValue valueWithBytes:&floats objCType:@encode(ThreeFloats)];
[person setValue:value forKey:@"threeFloats"];
NSValue *value1 = [person valueForKey:@"threeFloats"];
ThreeFloats th;
[value1 getValue:&th];
NSLog(@"结构体threeFloats的值为:%f-%f-%f",th.x,th.y,th.z);
}
数组设值取值
- (void)arrayTest{
WYStudent *student = [[WYStudent alloc] init];
student.penArr = [NSMutableArray arrayWithObjects:@"pen0", @"pen1", @"pen2", @"pen3", nil];
NSArray *arr = [student valueForKey:@"penArr"]; // 动态成员变量
NSLog(@"array的值为:%@",arr);
}
字典设值取值
- (void)dictionaryTest{
NSDictionary* dict = @{
@"name":@"zwy",
@"age":@18,
@"length":@180
};
WYStudent *student = [[WYStudent alloc] init];
// 字典转模型
[student setValuesForKeysWithDictionary:dict];
NSLog(@"student:%@",student);
// 键数组转模型到字典,只获取其中的某些属性组成字典
NSArray *array = @[@"name",@"age"];
NSDictionary *dic = [student dictionaryWithValuesForKeys:array];
NSLog(@"字典:%@",dic);
}
消息传递
//可以对数组的元素进行操作,并且返回一个数组
//直接通过keypath传入元素的属性
- (void)arrayMessagePass{
NSArray *array = @[@"Hank",@"Cooci",@"Kody",@"CC",@"13"];
NSArray *lenStr= [array valueForKeyPath:@"length"];
// 消息从array传递给了string,所以获取的并不是数组的长度,而是数组中字符串的长度
NSLog(@"每个字符串的长度:%@",lenStr);
NSArray *lowStr= [array valueForKeyPath:@"lowercaseString"];
NSLog(@"%@",lowStr);
NSArray *doubleArray = [array valueForKeyPath:@"doubleValue"];
NSLog(@"%@",doubleArray);
WYPerson *p1 = [[WYPerson alloc] init];
p1.name = @"p1";
p1.age = 12;
WYPerson *p2 = [[WYPerson alloc] init];
p2.name = @"p2";
p2.age = 13;
WYPerson *p3 = [[WYPerson alloc] init];
p3.name = @"pfsafd";
p3.age = 15;
NSArray *pArray = @[p1,p2,p3];
NSArray *name = [pArray valueForKeyPath:@"name"];
NSLog(@"name--%@",name);
NSArray *age = [pArray valueForKeyPath:@"age"];
NSLog(@"age--%@",age);
}
聚合操作符
//聚合操作符
// @avg、@count、@max、@min、@sum
- (void)aggregationOperator{
NSMutableArray *personArray = [NSMutableArray array];
for (int i = 0; i < 6; i++) {
WYStudent *p = [WYStudent 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);
}
数组操作符
// 数组操作符 @distinctUnionOfObjects @unionOfObjects
- (void)arrayOperator{
NSMutableArray *personArray = [NSMutableArray array];
for (int i = 0; i < 6; i++) {
WYStudent *p = [WYStudent 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.length"];
NSLog(@"arr1 = %@", arr1);
// 返回操作对象指定属性的集合 -- 去重
NSArray* arr2 = [personArray valueForKeyPath:@"@distinctUnionOfObjects.length"];
NSLog(@"arr2 = %@", arr2);
}
嵌套集合操作
// 嵌套集合(array&set)操作 @distinctUnionOfArrays @unionOfArrays @distinctUnionOfSets
- (void)arrayNesting{
NSMutableArray *personArray1 = [NSMutableArray array];
for (int i = 0; i < 6; i++) {
WYStudent *student = [WYStudent 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++) {
WYStudent *person = [WYStudent 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);
}
- (void)setNesting{
NSMutableSet *personSet1 = [NSMutableSet set];
for (int i = 0; i < 6; i++) {
WYStudent *person = [WYStudent 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++) {
WYStudent *person = [WYStudent 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);
}
输出结果:
2021-10-23 20:24:30.059592+0800 KVO分析[42437:630511] person的name为:zwy
2021-10-23 20:24:30.059719+0800 KVO分析[42437:630511] person下的student的name为:studentWY
2021-10-23 20:24:30.060221+0800 KVO分析[42437:630511] 结构体threeFloats的值为:1.000000-2.000000-3.000000
2021-10-23 20:24:30.060329+0800 KVO分析[42437:630511] array的值为:(
pen0,
pen1,
pen2,
pen3
)
2021-10-23 20:24:30.060713+0800 KVO分析[42437:630511] student:<WYStudent: 0x600001216d30>
2021-10-23 20:24:30.060833+0800 KVO分析[42437:630511] 字典:{
age = 18;
name = zwy;
}
2021-10-23 20:24:30.061317+0800 KVO分析[42437:630511] 每个字符串的长度:(
4,
5,
4,
2,
2
)
2021-10-23 20:24:30.061598+0800 KVO分析[42437:630511] (
hank,
cooci,
kody,
cc,
13
)
2021-10-23 20:24:30.061713+0800 KVO分析[42437:630511] (
0,
0,
0,
0,
13
)
2021-10-23 20:24:30.061819+0800 KVO分析[42437:630511] name--(
p1,
p2,
pfsafd
)
2021-10-23 20:24:30.061913+0800 KVO分析[42437:630511] age--(
12,
13,
15
)
2021-10-23 20:24:30.062014+0800 KVO分析[42437:630511] (
175,
175,
183,
181,
175,
175
)
2021-10-23 20:24:30.062535+0800 KVO分析[42437:630511] 177.333328
2021-10-23 20:24:30.062793+0800 KVO分析[42437:630511] 6
2021-10-23 20:24:30.062879+0800 KVO分析[42437:630511] 1064
2021-10-23 20:24:30.063111+0800 KVO分析[42437:630511] 183
2021-10-23 20:24:30.072207+0800 KVO分析[42437:630511] 175
2021-10-23 20:24:30.072368+0800 KVO分析[42437:630511] (
183,
181,
179,
179,
179,
183
)
2021-10-23 20:24:30.072933+0800 KVO分析[42437:630511] arr1 = (
183,
181,
179,
179,
179,
183
)
2021-10-23 20:24:30.073093+0800 KVO分析[42437:630511] arr2 = (
181,
183,
179
)
2021-10-23 20:24:30.073280+0800 KVO分析[42437:630511] arr = (
185,
179,
175
)
2021-10-23 20:24:30.073412+0800 KVO分析[42437:630511] arr1 = (
179,
175,
185,
175,
185,
175,
175,
179,
179,
179,
179,
175
)
2021-10-23 20:24:30.073542+0800 KVO分析[42437:630511] personSet1 = {(
175,
181,
183
)}
2021-10-23 20:24:30.073640+0800 KVO分析[42437:630511] personSet2 = {(
181,
177,
183,
179
)}
2021-10-23 20:24:30.073777+0800 KVO分析[42437:630511] arr1 = {(
175,
181,
177,
183,
179
)}
设值原理分析
底层实现过程通过查看官方文档分析Key-Value Coding Programming Guide
说明:
1、先顺序查找set<Key>->_set<Key>->setIs<Key>方法,如果有则直接赋值
2、如果没有先判断accessInstanceVariablesDirectly是否允许访问成员变量方法返回为YES,返回NO直接报错
3、如果返回YES则查找实例变量,顺序查找_key、_isKey、key、isKey,如果有则赋值
4、如果没有则调用setValue:forUndefinedKey:崩溃
取值原理分析
说明:
1、先顺序查找get< Key> ->< key>->is< Key>->_< key>方法,查找成功就直接取值
2、查找失败判断accessInstanceVariablesDirectly是否允许访问成员变量方法返回为YES,返回NO直接报错
3、如果返回YES则查找实例变量,顺序查找_key、_isKey、key、isKey,如果有则取值
4、如果查找不到,就调用valueForUndefinedKey报错
对于集合类型会提供特有的getter方法 (可以先不用理会)
包括:countOfXXX / objectInXXXAtIndex / enumeratorOfXXX
设置方法:
//MARK: - 集合类型的走
// 个数
- (NSUInteger)countOfPens{
NSLog(@"%s",__func__);
return [self.arr count];
}
// 获取值
- (id)objectInPensAtIndex:(NSUInteger)index {
NSLog(@"%s",__func__);
return [NSString stringWithFormat:@"pens %lu", index];
}
//MARK: - set
// 个数
- (NSUInteger)countOfBooks{
NSLog(@"%s",__func__);
return [self.set count];
}
// 是否包含这个成员对象
- (id)memberOfBooks:(id)object {
NSLog(@"%s",__func__);
return [self.set containsObject:object] ? object : nil;
}
// 迭代器
- (id)enumeratorOfBooks {
// objectEnumerator
NSLog(@"来了 迭代编译");
return [self.arr reverseObjectEnumerator];
}
调用:
- (void)arraysAndSet{
LGPerson *person = [[LGPerson alloc] init];
// 3: KVC - 集合类型
person.arr = @[@"pen0", @"pen1", @"pen2", @"pen3"];
NSArray *array = [person valueForKey:@"pens"];
// NSLog(@"%@",[array objectAtIndex:1]);
// NSLog(@"%d",[array containsObject:@"pen1"]);
// NSLog(@"%d",[array count]);
// set 集合
// person.set = [NSSet setWithArray:person.arr];
// NSSet *set = [person valueForKey:@"books"];
// [set enumerateObjectsUsingBlock:^(id _Nonnull obj, BOOL * _Nonnull stop) {
// NSLog(@"set遍历 %@",obj);
// }];
}
注:不管是取值还是赋值,都实现判断属性,之后判断成员变量,还有注意大小写
自定义KVC
上面我们知道了系统设置KVC的过程,我们可以根据系统提供的流程增加自己的一些逻辑来自定义。
自定义setValue
源码:
- (void)lg_setValue:(nullable id)value forKey:(NSString *)key{
// KVC 自定义
// 1: 判断什么 key
if (key == nil || key.length == 0) {
return;
}
// 2: setter set<Key>: or _set<Key>,setIs<Key>
// key 要大写
NSString *Key = key.capitalizedString;
// 拼接方法
NSString *setKey = [NSString stringWithFormat:@"set%@:",Key];
NSString *_setKey = [NSString stringWithFormat:@"_set%@:",Key];
NSString *setIsKey = [NSString stringWithFormat:@"setIs%@:",Key];
if ([self lg_performSelectorWithMethodName:setKey value:value]) {
NSLog(@"*********%@**********",setKey);
return;
}else if ([self lg_performSelectorWithMethodName:_setKey value:value]) {
NSLog(@"*********%@**********",_setKey);
return;
}else if ([self lg_performSelectorWithMethodName:setIsKey value:value]) {
NSLog(@"*********%@**********",setIsKey);
return;
}
// 3: 判断是否响应 accessInstanceVariablesDirectly 返回YES NO 奔溃
// 3:判断是否能够直接赋值实例变量
if (![self.class accessInstanceVariablesDirectly] ) {
@throw [NSException exceptionWithName:@"LGUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ valueForUndefinedKey:]: this class is not key value coding-compliant for the key name.****",self] userInfo:nil];
}
// 4: 间接变量
// 获取 ivar -> 遍历 containsObjct -
// 4.1 定义一个收集实例变量的可变数组
NSMutableArray *mArray = [self getIvarListName];
// _<key> _is<Key> <key> is<Key>
NSString *_key = [NSString stringWithFormat:@"_%@",key];
NSString *_isKey = [NSString stringWithFormat:@"_is%@",Key];
NSString *isKey = [NSString stringWithFormat:@"is%@",Key];
if ([mArray containsObject:_key]) {
// 4.2 获取相应的 ivar
Ivar ivar = class_getInstanceVariable([self class], _key.UTF8String);
// 4.3 对相应的 ivar 设置值
object_setIvar(self , ivar, value);
return;
}else if ([mArray containsObject:_isKey]) {
Ivar ivar = class_getInstanceVariable([self class], _isKey.UTF8String);
object_setIvar(self , ivar, value);
return;
}else if ([mArray containsObject:key]) {
Ivar ivar = class_getInstanceVariable([self class], key.UTF8String);
object_setIvar(self , ivar, value);
return;
}else if ([mArray containsObject:isKey]) {
Ivar ivar = class_getInstanceVariable([self class], isKey.UTF8String);
object_setIvar(self , ivar, value);
return;
}
// 5:如果找不到相关实例
@throw [NSException exceptionWithName:@"LGUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ %@]: this class is not key value coding-compliant for the key name.****",self,NSStringFromSelector(_cmd)] userInfo:nil];
}
说明:
- 先判断key是否可用
- 分别查找类中是否存在set<Key>, _set<Key>,setIs<Key>方法
- 判断accessInstanceVariablesDirectly返回值是否为YES
- 获取到类中所有的变量,按照顺序_XXX,_isXXX,XXX,isXXX的顺序判断变量是否存在,存在就赋值
- 如果最终没有获取到,就报错
自定义valueForKey
源码:
(nullable id)lg_valueForKey:(NSString *)key{
// 1:刷选key 判断非空
if (key == nil || key.length == 0) {
return nil;
}
// 2:找到相关方法 get<Key> <key> countOf<Key> objectIn<Key>AtIndex
// key 要大写
NSString *Key = key.capitalizedString;
// 拼接方法
NSString *getKey = [NSString stringWithFormat:@"get%@",Key];
NSString *countOfKey = [NSString stringWithFormat:@"countOf%@",Key];
NSString *objectInKeyAtIndex = [NSString stringWithFormat:@"objectIn%@AtIndex:",Key];
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
if ([self respondsToSelector:NSSelectorFromString(getKey)]) {
return [self performSelector:NSSelectorFromString(getKey)];
}else if ([self respondsToSelector:NSSelectorFromString(key)]){
return [self performSelector:NSSelectorFromString(key)];
}else if ([self respondsToSelector:NSSelectorFromString(countOfKey)]){
if ([self respondsToSelector:NSSelectorFromString(objectInKeyAtIndex)]) {
int num = (int)[self performSelector:NSSelectorFromString(countOfKey)];
NSMutableArray *mArray = [NSMutableArray arrayWithCapacity:1];
for (int i = 0; i<num-1; i++) {
num = (int)[self performSelector:NSSelectorFromString(countOfKey)];
}
for (int j = 0; j<num; j++) {
id objc = [self performSelector:NSSelectorFromString(objectInKeyAtIndex) withObject:@(num)];
[mArray addObject:objc];
}
return mArray;
}
}
#pragma clang diagnostic pop
// 3:判断是否能够直接赋值实例变量
if (![self.class accessInstanceVariablesDirectly] ) {
@throw [NSException exceptionWithName:@"LGUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ valueForUndefinedKey:]: this class is not key value coding-compliant for the key name.****",self] userInfo:nil];
}
// 4.找相关实例变量进行赋值
// 4.1 定义一个收集实例变量的可变数组
NSMutableArray *mArray = [self getIvarListName];
// _<key> _is<Key> <key> is<Key>
// _name -> _isName -> name -> isName
NSString *_key = [NSString stringWithFormat:@"_%@",key];
NSString *_isKey = [NSString stringWithFormat:@"_is%@",Key];
NSString *isKey = [NSString stringWithFormat:@"is%@",Key];
if ([mArray containsObject:_key]) {
Ivar ivar = class_getInstanceVariable([self class], _key.UTF8String);
return object_getIvar(self, ivar);;
}else if ([mArray containsObject:_isKey]) {
Ivar ivar = class_getInstanceVariable([self class], _isKey.UTF8String);
return object_getIvar(self, ivar);;
}else if ([mArray containsObject:key]) {
Ivar ivar = class_getInstanceVariable([self class], key.UTF8String);
return object_getIvar(self, ivar);;
}else if ([mArray containsObject:isKey]) {
Ivar ivar = class_getInstanceVariable([self class], isKey.UTF8String);
return object_getIvar(self, ivar);;
}
return @"";
}
说明:
- 先判断key是否可用
- 分别查找类中是否存在get<Key>, <Key>方法
- 再判断是否存在countOfKey和objectInKeyAtIndex,如果存在则按照countOfKey、objectInKeyAtIndex的顺序来调用
- 判断accessInstanceVariablesDirectly是否可以获取成员变量
- 获取到所有的成员变量,按照_key,_isKey,isKey的顺序赋值
使用场景
- 动态设置和取值
- 可以通过字符串来设置,所以可以动态化,从外部或plist文件中来设置,而不修改源代码
- 通过KVC访问和修饰私有变量
- 对于类的私有属性、变量,外部是无法直接访问的,但是通过KVC是可以直接修改、访问任何的私有属性和成员变量的
- 多值操作(model和字典互转)
- 字典和模型的转换,比较方便的一个操作
- 字典转模型
- 模型转字典
- 修改一些系统控件的内部属性
- 有一些控件是由多个控件组成的,而这些内部控件苹果并没有提供访问API
- 因此可以使用KVC来解决这个问题,可以修改、访问所有控件的属性
- 使用举例,自定义tabbar、个性化UITextField中的placeHolderText
- 用KVC实现高阶消息传递
- 对容器类使用KVC时,valueForKey会作用于容器中的每个元素,而不是作用于容器本身
- 并且将其对每个元素进行获取后还会返回一个容器
- 这样就可以很方便的对一个容器进行操作并返回一个容器。