1.kvc的基本用法
其实kvc是一个用起来非常简单的东西,并没有想象的那么复杂,他的作用就是根据属性的名字(key值)给一个属性赋值,或者是根据属性名(key值)给获取属性值。
赋值:
- (void)setValue:(nullable id)value forKey:(NSString *)key;
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
取值:
- (nullable id)valueForKeyPath:(NSString *)keyPath;
- (nullable id)valueForKey:(NSString *)key;
keyPath和key的区别是什么?
❓比如说有一个类叫做address,address这个类里有一个属性叫做country,我们可以直接通过setValueForKeyPath:@"address.country"这个方法给country属性赋值,而不需要先把address取出来再用setValueForKey方法给country赋值。
2.kvc的好处
当我们用@property声明一个属性的时候,系统会自动生成成员变量+get方法+set方法,这个时候,可以用点语法(set、get方法)给属性赋值和取值。但是如果这个属性写在类的.m文件里,其他文件就无法通过点语法去取值和赋值。
用kvo可以给类的私有属性或成员变量赋值。
3.kvc赋值的底层实现
为什么kvc可以根据key值就能修改对应属性的值呢?
当调用方法setValue:属性值 forKey:@”name“的时候,底层实现的顺序如下:
3.1.优先调用setName方法
3.2.1 如果没有找到set方法(如果不是kvc的话,其他类里找不到这个属性的set方法就没办法给他赋值了),会检查+ (BOOL)accessInstanceVariablesDirectly有没有返回yes,这个方法默认返回的是yes。如果这个手动把accessInstanceVariablesDirectly这个方法的返回值设置成no的话,系统就不会继续往下查找了,直接调用-(void)setValue:(id)value forUndefinedKey:(NSString *)key这个方法,如果重写了这个方法程序就不会崩溃,会执行这个方法,如果没有重写这个方法,程序就会崩溃。
🙋🌰:
@implementation numClass{
NSString* isMarry;
}
这个成员变量没有set方法,如果这个时候accessInstanceVariablesDirectly这个方法的返回值设置成no,还不重写(void)setValue:(id)value forUndefinedKey:(NSString *)key这个方法,程序就会崩溃,如果重写了就不会崩溃。
3.2.2正常情况下,是不会像上面那样做的,找不到setName方法系统就去找+ (BOOL)accessInstanceVariablesDirectly方法有没有返回yes,如果返回了yes的话,就去找有没有叫做_name的成员变量,只要有就可以赋值。
3.3如果还是没有,就会去找有没有叫做_isName的成员变量。
3.4如果还没有,就继续找有没有叫做name或isName的成员变量。⚠️:一定要大写,不大写找不到
3.5如果上面的那些成员变量都没有找到,就调用(void)setValue:(id)value forUndefinedKey:(NSString *)key这个方法,如果不重写程序就崩了。
4.kvc取值的底层实现
当调用valueForKey:@”name“的代码时,取值的时候查找顺序和赋值不一样,按照如下顺序:
4.1按照getName,name,isName的顺序先去调用name的get方法,如果找到了就会直接调用,不会再往后面查。
4.2如果没找到,会按顺序查找方法countOfName、objectInNameAtIndex或NameAtIndexes格式的方法。按照这几个方法的实现给name赋值。
4.3如果还没有找到,就会去检查+ (BOOL)accessInstanceVariablesDirectly,如果返回YES(默认行为),那么和先前的设值一样,会按_name,_isName,name,isName的顺序搜索成员变量名,这里不推荐这么做,因为这样直接访问实例变量破坏了封装性,使代码更脆弱。如果重写了类方法+ (BOOL)accessInstanceVariablesDirectly返回NO的话,那么会直接调用valueForUndefinedKey:
5.KVC处理非对象和自定义对象
KVC在取值和赋值的时候,都是使用对象。
valueForKey:这个方法,总是返回id类型的对象。如果原本的变量类型是值类型或者结构体,返回值会封装成NSNumber或者NSValue对象。开发者手动转化成原来的类型(比如说把NSNumber转成NSInteger)。
setValue:forKey:方法传值的时候,必须把传递的值转化成对象才能传进去。
6.KVC与容器类
如果访问的容器是一个不可变容器,不可变有序容器(NSArray)或不可变无序容器(NSSet)可以用valueForKey:来取值。
如果访问的是一个可变容器,比如NSMutableArray,用- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;方法取值。
7.KVC与字典
dictionaryWithValuesForKeys:NSArray
🌰:Address* add = [Address new];
add.country =@"China";
add.province =@"Guang Dong";
add.city =@"Shen Zhen";
add.district =@"Nan Shan";
NSArray* arr = @[@"country",@"province",@"city",@"district"];
NSDictionary* dict = [add dictionaryWithValuesForKeys:arr]; //把对应key所有的属性全部取出来
打印出来的dict就是这样的city = "Shen Zhen"; country = China; district = "Nan Shan"; province = "Guang Dong";
setValuesForKeysWithDictionary
NSDictionary* modifyDict = @{@"country":@"USA",@"province":@"california",@"city":@"Los angle"};
[add setValuesForKeysWithDictionary:modifyDict];//用key Value来修改Model的属性NSLog(@"country:%@ province:%@ city:%@",add.country,add.province,add.city);
打印出来是这样的 country:USA province:california city:Los angle
8.KVC在iOS开发中的应用场景
8.1动态的取值和赋值
8.2访问类的私有属性和成员变量
8.3结合运行时,实现字典转模型
8.4修改原生控件的一些属性,因为原生控件的api不可见,只有用kvc才能修改内部的属性。可以用runtime获取苹果没有开放的属性名。
8.5KVC提供了一些简单的aggregation函数,就像数据库里一样。
NSNumber* sum = [arrBooks valueForKeyPath:@"@sum.price"];
NSNumber* avg = [arrBooks valueForKeyPath:@"@avg.price"];
网友评论