KVC简介:
KVC(key - value codeing),是苹果提供的一套基于运行时的编码技术。它允许开发者直接通过key值来访问对象对应的属性,而无需明确调用set、get方法,所以kvc可以在运行时动态的修改对象的属性,这也是他的强大之处。若对象没有实现属性的set、get方法,那么可以直接使用kvc来存取其属性值,无论该变量是在类接口处定义,还是在类实现处定义,无论用了什么样的访问修饰符,只在存在以命名的变量,KVC都可以对该成员变量赋值。若属性实现了set、get方法则尽量通过set、get方法来存取值,这样不会破坏代码的封装性,也更加方便安全高效。KVC的定义都是在NSObject的的扩展中实现的(NSKeyValueCodeing类别)。所以继承了NSObject的对象才可以使用KVC,一些纯swift类的结构体是不能使用KVC的。
以下是KVC一些常用的主要方法:
//通过Key来取值
通过Key来设值,可以为NSMutableDictionary赋值,也可以为对象的属性赋值,如字典转模型,若属性类型为对象value值可以为nil,否则不能为nil,因为值类型不能为nil。若value为数字类型不可以直接设置需要转换为NSNumber对象再设置,value为结构体时需要转换为NSValues对象才能赋值。
通过KeyPath来取值,可以使用keypath路径来对深层次的数据进行访问。
通过KeyPath赋值,KeyPath显得更为强大,可以作为kvc的取值,更强大的是可以使用它对集合中的对象进行各种方法的处理,例如提取最大值,最小值,平均值,去重等处理,示例如下(KVC的集合操作):
NSArray *array = @[@3, @6, @7, @8, @10];
//获取集合中的和
NSNumber *sum = [array valueForKeyPath:@"@sum.self"];
//获取集合中的平均值
NSNumber *avg = [array valueForKeyPath:@"@avg.self"];
//获取集合中的最大值
NSNumber *max = [array valueForKeyPath:@"@max.self"];
//获取集合中的最小值
NSNumber *min = [array valueForKeyPath:@"@min.self"];
//指定输出类型
NSNumber *sum = [array valueForKeyPath:@"@sum.floatValue"];
NSNumber *avg = [array valueForKeyPath:@"@avg.floatValue"];
NSNumber *max = [array valueForKeyPath:@"@max.floatValue"];
NSNumber *min = [array valueForKeyPath:@"@min.floatValue"];
//去重 对象运算符:@distinctUnionOfObjects @unionOfObjects
NSArray * txArr = @[@"aa", @"bb",@"cc",@"aa", @"cc"];
NSLog(@"txArr : %@", [txArr valueForKeyPath:@"@distinctUnionOfObjects.self"]);
//嵌套使用:先对name去重再取出对应的name值
NSArray * dicArr = @[@{@"zhang":@"wang", @"age":@12, @"gender":@"man"},
@{@"name":@"zhang", @"age":@12, @"gender":@"woman"},
@{@"name":@"li", @"age":@12, @"gender":@"man"}];
NSLog(@"txArr : %@", [txArr valueForKeyPath:@"@distinctUnionOfObjects.name"]);
//更改uitextFile的placeholder字体颜色
[searchField setValue:[UIColor whiteColor] forKeyPath:@"_placeholderLabel.textColor"];
KVC中一些其他的重要方法:
默认返回YES,在运行时若未找到属性的Set方法时,会按照_key,_iskey,key,iskey的顺序搜索成员,设置成NO会对此类禁用KVC,若未找到set,get方法直接抛出异常。
KVC验证属性值的API,它可以用来检查set的值是否正确、可以添加值不正确时的处理逻辑。
集合操作的API,若属性是一个NSMutableArray,那么可以用这个方法获取。
取值时如果Key不存在,会调用这个方法,默认是抛出异常,可用于处理异常。
设值时若key不存在会调用,抛出异常,可用于处理异常。
SetValue时面给Value传nil,会调用这个方法.
传入一组key,返回该组key对应的Value,并转成字典返回,可用于Model转字典。
用来修改Model中对应key的属性,用于字典转模型。
注意: setObject: forKey: 是NSMutableDictionary特有的方法,value不能为nil/null,但可以为[NSNull null](因为它为对象类型),必须为对象。
以下为场景示例:
以下示例中,Cameras类有两个NiCon类型的属性,其中nicon1为公有的属性,nicon1 未自动生成set、get方法;nicon2为私有的属性;NiCon类有一个公有属性devName,一个私有属性devNO;
// Cameras.h
#import
#import "NiCon.h"
NS_ASSUME_NONNULL_BEGIN
@interface Cameras : NSObject
@property(nonatomic,strong)NiCon * nicon1;
@end
NS_ASSUME_NONNULL_END
//Cameras.m
#import "Cameras.h"
@interface Cameras()
@property(nonatomic,strong)NiCon * nicon2;
@end
@implementation Cameras
//让nicon1不要自动生成set、get方法;
@synthesize nicon1;
- (instancetype)init
{
self = [super init];
if (self) {
}
return self;
}
- (id)valueForUndefinedKey:(NSString *)key{
NSLog(@"该key: %@ 不存在", key);
return nil;
}
-(void)setValue:(id)value forUndefinedKey:(NSString *)key{
NSLog(@"该key值: %@ 不存在", key);
}
+(BOOL)accessInstanceVariablesDirectly{
// return NO;
// 2019-03-11 16:49:44.899991+0800 test[2256:72136] 该key值: nicon1 不存在
// 2019-03-11 16:49:44.900311+0800 test[2256:72136] nicon1: (null)
return YES;
// 2019-03-11 16:44:29.408502+0800 test[2112:64508] nicon1:
// 2019-03-11 16:44:29.408724+0800 test[2112:64508] nicon2:
// 2019-03-11 16:44:29.409306+0800 test[2112:64508] nicon1.devName: 尼康X525
// 2019-03-11 16:44:29.409462+0800 test[2112:64508] nicon2.devName: 尼康X525
}
@end
第二个类:
//NiCon.h
#import
NS_ASSUME_NONNULL_BEGIN
@interface NiCon : NSObject
@property(nonatomic, copy)NSString * devName;
@end
NS_ASSUME_NONNULL_END
//NiCon.m
#import "NiCon.h"
@interface NiCon()
@property(nonatomic,assign) int devNO;
@end
@implementation NiCon
-(BOOL)validateValue:(inout id _Nullable __autoreleasing *)ioValue forKey:(NSString *)inKey error:(out NSError * _Nullable __autoreleasing *)outError{
return [inKey isEqualToString:@"devNO"];
}
@end
接下来我们在viewController中使用KVC来对对象的属性进行赋值:
//viewController.m
- (void)viewDidLoad {
[super viewDidLoad];
Cameras* cams = [[Cameras alloc]init];
NiCon * nic1 = [[NiCon alloc]init];
//设置公有属性
[cams setValue:nic1 forKey:@"nicon1"];
NSLog(@"nicon1: %@", cams.nicon1);
// 2019-03-11 16:06:01.305184+0800 test[876:12560] nicon1:
//设置私有属性
[cams setValue:nic1 forKey:@"nicon2"];
NSLog(@"nicon2: %@", [cams valueForKey:@"nicon2"]);
// 2019-03-11 16:09:26.472743+0800 test[999:20027] nicon2:
}
从以上示例可以看出,无论是公有属性还是私有属性,KVC都可以成功的赋值和取值,所以KVC是不受访问修饰符限制的。
接下来我们分别将Cameras的属性nicon1的属性名改为_nicon1、isNicon1,并将+(BOOL)accessInstanceVariablesDirectly返回值分别设为YES和NO看能否赋值成功?
//Cameras.h
//@property(nonatomic,strong)NiCon * _nicon1;
//@property(nonatomic,strong)NiCon * isNicon1;
//NiCon.m
//如果Key不存在,会调用这两方法,默认是抛出异常,可用于处理异常。
- (id)valueForUndefinedKey:(NSString *)key{
NSLog(@"该key: %@ 不存在", key);
return nil;
}
-(void)setValue:(id)value forUndefinedKey:(NSString *)key{
NSLog(@"该key值: %@ 不存在", key);
}
+(BOOL)accessInstanceVariablesDirectly{
// return NO;
return YES;
- (void)viewDidLoad {
[super viewDidLoad];
Cameras* cams = [[Cameras alloc]init];
NiCon * nic1 = [[NiCon alloc]init];
//设置公有属性
[cams setValue:nic1 forKey:@"nicon1"];
NSLog(@"nicon1: %@", cams._nicon1);
// return NO;
// 2019-03-11 16:49:44.899991+0800 test[2256:72136] 该key值: nicon1 不存在
// 2019-03-11 16:49:44.900311+0800 test[2256:72136] nicon1: (null)
// return YES;
// 2019-03-11 16:44:29.408502+0800 test[2112:64508] nicon1:
// 2019-03-11 16:44:29.408724+0800 test[2112:64508] nicon2:
//设置公有属性
[cams setValue:nic1 forKey:@"nicon1"];
NSLog(@"nicon1: %@", cams.isNicon1);
// return NO;
// 2019-03-11 16:49:44.899991+0800 test[2256:72136] 该key值: nicon1 不存在
// 2019-03-11 16:49:44.900311+0800 test[2256:72136] nicon1: (null)
// return YES;
// 2019-03-11 16:44:29.408502+0800 test[2112:64508] nicon1:
// 2019-03-11 16:44:29.408724+0800 test[2112:64508] nicon2:
}
以上测试验证了+(BOOL)accessInstanceVariablesDirectly;
当返回值设为YES(默认),在运行时若未找到属性的Set方法时,会按照_key,_iskey,key,iskey的顺序搜索成员变量,设置成NO会对此类禁用KVC搜索,若未找到set,get方法直接抛出异常。
使用KVC的setValue:forKeyPath:设置对象的嵌套属性:
- (void)viewDidLoad {
[super viewDidLoad];
Cameras* cams = [[Cameras alloc]init];
NiCon * nic1 = [[NiCon alloc]init];
//使用keyPath设置公有嵌套属性
[cams setValue:@"尼康X525" forKeyPath:@"nicon1.devName"];
NSLog(@"nicon1.devName: %@", [cams valueForKeyPath:@"nicon1.devName"]);
// test[1380:36444] nicon1.devName: 尼康X525
//使用keyPath设置私有嵌套属性
[cams setValue:@"尼康X525" forKeyPath:@"nicon2.devName"];
NSLog(@"nicon2.devName: %@", [cams valueForKeyPath:@"nicon2.devName"]);
// test[1540:43465] nicon2.devName: 尼康X525
由此可见KVC对与公有私有的嵌套属性,也是从来不惧,哈哈就是这么强大!
使用KVC的键值验证:- (BOOL)validateValue:(inout id __nullable * __nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;
//NiCon.m
@implementation NiCon
-(BOOL)validateValue:(inout id _Nullable __autoreleasing *)ioValue forKey:(NSString *)inKey error:(out NSError * _Nullable __autoreleasing *)outError{
return [inKey isEqualToString:@"devNO"];
}
//viewController.m
NSNumber * num = [NSNumber numberWithInteger:1011];
if ([cams validateValue:&num forKey:@"nicon2.devNO" error:nil]) {//使用KVC键值验证属性如果键名是正确的再赋值
[cams setValue:num forKeyPath:@"nicon2.devNO"];
NSLog(@"nicon2.devNO: %@", [cams valueForKeyPath:@"nicon2.devNO"]);
//log: test[6716:263169] nicon2.devNO: 1011
}
模型和字典互转:
Cameras* cams = [[Cameras alloc]init];
//模型转字典
NSArray * modelKeys = @[@"devName", @"devNO"];
NSDictionary * modelDic = [[cams valueForKey:@"nicon2"] dictionaryWithValuesForKeys:modelKeys]; NSLog(@"modelDic: %@", modelDic);
/* 2019-03-12 09:53:33.489429+0800 test[6716:263169] modelDic: {
devNO = 1011;
devName = "\U5c3c\U5eb7X525";
}
*/
//字典转模型
NiCon * nicon3 = [[NiCon alloc]init];
[nicon3 setValuesForKeysWithDictionary:modelDic];
NSLog(@"devName: %@ devNO: %@",nicon3.devName, [nicon3 valueForKey:@"devNO"]);
// 2019-03-12 09:53:33.489696+0800 test[6716:263169] devName: 尼康X525 devNO: 1011
KVC确实很强大,但是万事皆有度,不可过分,否则会适得其反。