KVC(Key Value Coding)是Cocoa框架为开发者提供的很是强大的工具,简单解释为:键值编码。它依赖于Runtime,在OC的动态性方面发挥了重要做用。
它主要的功能在于直接经过变量名称字符串来访问成员变量,不论是私有的仍是共有的,这也是为何对于OC来讲没有真正的私有变量,由于它们均可以使用KVC访问。html
下面是KVC的一些实用场景,读者可自行编码尝试。git
例如设置UITextField的placeholder颜色,常规的方法是:github
// 方式一:常规设置 _nameTextField.attributedPlaceholder = [[NSAttributedString alloc] initWithString:@"请输入名字" attributes:@{NSForegroundColorAttributeName : [UIColor redColor]}];
经过KVC的方式设置:数组
// 方式二:使用KVC获取私有属性 _nameTextField.placeholder = @"请输入名字"; // UILabel *placeHolderLabel = [_nameTextField valueForKey:@"placeholderLabel"]; UILabel *placeHolderLabel = [_nameTextField valueForKey:@"_placeholderLabel"]; placeHolderLabel.textColor = UIColor.blackColor; [self.view addSubview:_nameTextField];
这里经过valueForKey的方式获取了UITextField的placeholderLabel,而后对该对象的颜色进行设置。若是获取到的placeholderLabel为nil,有几种可能:安全
仍是建议使用使用方式一对属性进行设置。站在苹果的角度,它之因此把某些属性设置为私有,就是不想让开发者进行直接修改,后续一旦苹果对系统实现有所更改,那就会致使使用KVC获取的内容失效。
另外,在使用setValue:forKey:的时候必定要类型统一,好比你经过key获取到的是一个Label,却将string设置为了value,将会crash。
上面在使用valueForKey:方法的时候参数能够带下划线( _ placeholderLabel ),也能够不带下划线,它的主要区别就是若是使用了带下划线的key,就算类中手动实现了getter方法,也不会执行类中实现的getter方法。若是使用了不带下划线的,将会执行类中getter方法。setter也是如此。app
在没有比较成熟的第三方Model解析(如Mantle)前,ORM(Object Relational Mapping)可使用KVC进行处理:框架
- (instancetype)init { return [self initWithJSONDictionary:nil]; } - (instancetype)initWithJSONDictionary:(NSDictionary *)anDictionary { if (self = [super init]) { [self setValuesForKeysWithDictionary:anDictionary]; } return self; } - (void)setValue:(id)value forUndefinedKey:(NSString *)key { NSLog(@"%@",key); } // - (void)objectRelationalMapping { NSDictionary *personInfoDictionary = @{ @"name" : @"zhangsan", @"age" : @"30", @"school" : @"Hist" }; Person *p1 = [[Person alloc] initWithJSONDictionary:personInfoDictionary]; NSLog(@"%@",p1); } @end
这里主要使用了setValuesForKeysWithDictionary:方法。须要注意的是,须要实现setValue:forUndefinedKey:方法,由于当字典中包含的key在Person属性中并不必定存在,若是不存在的话,就会调用setValue:forUndefinedKey:。该方法默认抛出NSUndefinedKeyException异常。因此须要对其进行重写,避免Crash。函数
KVC除了setValue:forKey:方法,还有setValue:forKeypath:方法。具体使用以下:工具
_nameTextField.placeholder = @"请输入名字"; [_nameTextField setValue:UIColor.blackColor forKeyPath:@"placeholderLabel.textColor"];
这里一步操做,就完成了对placeholder颜色的变动。ui
如今Person有一个friends方法,属性声明以下:
@property (nonatomic, copy) NSMutableArray *friends;
此时能够经过以下的方式进行设置friends:
NSArray *personsArray = ...; Person *zhangsan = [[Person alloc] init]; zhangsan.name = @"zhangsan"; [[zhangsan mutableArrayValueForKey:@"friends"] addObjectsFromArray:personsArray];
这样就能够顺利将personsArray赋值给zhangsan的friends。接下来换个操做:将属性改成
@property (nonatomic, copy) NSArray *friends;
其余代码保持不变。再次执行,依然会给zhangsan.friends赋值。并且没有任何crash或者异常。因而可知,经过mutableArrayValueForKey这种方式进行处理,能够对于不可变的集合类型,提供安全的可变访问,即便是不可变数组,也能够增长数组元素。
使用KVC,能够很方便地进行一些基本的函数操做,例如:
NSMutableArray *personsArray = [[NSMutableArray alloc] initWithCapacity:5]; for (NSInteger i = 0; i < 5; i ++) { NSString *tempName = [NSString stringWithFormat:@"people%ld",(long)i]; NSDictionary *personInfoDictionary = @{ @"name" : tempName, @"age" : @(10 + i), @"school" : @"Hist" }; Person *tempPerson = [[Person alloc] initWithJSONDictionary:personInfoDictionary]; [personsArray addObject:tempPerson]; } NSNumber *count = [personsArray valueForKeyPath:@"@count"]; NSNumber *sumAge = [personsArray valueForKeyPath:@"@sum.age"]; NSNumber *avgAge = [personsArray valueForKeyPath:@"@avg.age"]; NSNumber *maxAge = [personsArray valueForKeyPath:@"@max.age"]; NSNumber *minAge = [personsArray valueForKeyPath:@"@min.age"];
其中@表示是数组特有的键,而不是名为count的键。可使用valueForKeyPath:快速进行计算。还能够进行更复杂的一些计算:
NSArray *array = @[@"apple", @"banner", @"apple", @"orange"]; NSLog(@"%@", [array valueForKeyPath:@"@distinctUnionOfObjects.self"]); // orange,apple,banner NSArray *array1 = @[@[@"apple", @"banner"], @[@"apple", @"orange"]]; NSLog(@"%@", [array1 valueForKeyPath:@"@unionOfArrays.self"]); // apple,banner,apple,orange NSMutableArray *personsArray1 = [[NSMutableArray alloc] initWithCapacity:5]; NSMutableArray *personsArray2 = [[NSMutableArray alloc] initWithCapacity:5]; for (NSInteger i = 0; i < 5; i ++) { NSString *tempName = [NSString stringWithFormat:@"people%ld",(long)i]; NSDictionary *personInfoDictionary = @{ @"name" : tempName, @"age" : @(10 + i), @"school" : @"Hist" }; Person *tempPerson = [[Person alloc] initWithJSONDictionary:personInfoDictionary]; i % 2 == 0 ? [personsArray1 addObject:tempPerson] : [personsArray2 addObject:tempPerson]; } NSArray *personsArray = @[personsArray1, personsArray2]; NSLog(@"%@",[[personsArray valueForKeyPath:@"@unionOfArrays.age"] valueForKeyPath:@"@sum.self"]); // 60
相似上面的代码,可使用KVC对复杂的操做进行简单化,而没有必要再使用for循环或者其余遍历操做。
Person *person = [[Person alloc] init]; [person setValue:[UIColor redColor] forKey:@"name"];
name是string类型,可是传入一个UIColor类型也没有异常或者Crash产生,这也是一个潜在的问题,一旦按照string使用name,就会出现问题。所以须要对value类型与key是否匹配进行判断。KVC提供了以下的方法:
Person *person = [[Person alloc] init]; UIColor *color = [UIColor redColor]; NSError *error = nil; BOOL isValidate = [person validateValue:&color forKey:@"name" error:&error]; if (isValidate && !error) { [person setValue:color forKey:@"name"]; }
发现依然能够设置成功,validateValue:forKey:error:方法居然返回了YES。根据官方文档可知,此方法的默认实现将搜索接收方的类,寻找名称匹配validate
- (BOOL)validateName:(id *)ioValue error:(NSError **)error { if ([*ioValue isKindOfClass:[NSString class]]) { return YES; } return NO; }