一. 前言

KVC(Key Value Coding)是Cocoa框架为开发者提供的非常强大的工具,简单解释为:键值编码。它依赖于Runtime,在OC的动态性方面发挥了重要作用。

它主要的功能在于直接通过变量名称字符串来访问成员变量,不管是私有的还是共有的,这也是为什么对于OC来说没有真正的私有变量,因为它们都可以使用KVC访问。

二. 使用场景

下面是KVC的一些实用场景,读者可自行编码尝试。

1.访问私有属性

例如设置UITextField的placeholder颜色,常规的方法是:

 // 方式一:常规设置
_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,有几种可能:

  1. key输入有误(重新输入)
  2. 系统实现发生改变
  3. 苹果使用的Lazy Load,导致在使用valueForKey获取的时候还没有初始化(因此这里先赋值placeholder,然后再获取placeholderLabel)

还是建议使用使用方式一对属性进行设置。站在苹果的角度,它之所以把某些属性设置为私有,就是不想让开发者进行直接修改,后续一旦苹果对系统实现有所更改,那就会导致使用KVC获取的内容失效。

另外,在使用setValue:forKey:的时候一定要类型统一,比如你通过key获取到的是一个Label,却将string设置为了value,将会crash。

上面在使用valueForKey:方法的时候参数可以带下划线( _ placeholderLabel ),也可以不带下划线,它的主要区别就是如果使用了带下划线的key,就算类中手动实现了getter方法,也不会执行类中实现的getter方法。如果使用了不带下划线的,将会执行类中getter方法。setter也是如此。

2.对象关系映射

在没有比较成熟的第三方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。

3. 使用keyPath实现多级访问

KVC除了setValue:forKey:方法,还有setValue:forKeypath:方法。具体使用如下:

_nameTextField.placeholder = @"请输入名字";
[_nameTextField setValue:UIColor.blackColor forKeyPath:@"placeholderLabel.textColor"];

这里一步操作,就完成了对placeholder颜色的变更。

4. 安全性访问

现在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这种方式进行处理,可以对于不可变的集合类型,提供安全的可变访问,即使是不可变数组,也可以增加数组元素。

5. 函数操作

使用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循环或者其他遍历操作。

三. KVC验证

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:error:模式的验证方法。如果为属性定义了这样一个方法,那么validateValue:forKey:error: 的默认实现在需要被验证的时候调用。在为这个属性定义的方法中,你可以根据需要更改输入的值,或者设置默认值等。如果没有实现验证方法,则默认返回YES。因此,我们可以在Person类中实现下面的方法:

- (BOOL)validateName:(id *)ioValue error:(NSError **)error {
if ([*ioValue isKindOfClass:[NSString class]]) {
return YES;
}
return NO;
}

复习一下KVC的更多相关文章

  1. kvc/kvo复习

    kvc/kvo复习 1 小问题 '[<XMGPerson 0x7fb8a8f30220> setValue:forUndefinedKey:]: this XMGPerson * pers ...

  2. 06-IOSCore - KVC、CoreData

    一. KVC 1. KVC 使用前:黯淡无光 if ([keyPath isEqualToString:@"name"]) { self.labelName.text = self ...

  3. 阶段性总结⓵触摸事件&手势识别⓶Quartz2D绘图⓷CALayer图层⓸CAAnimation⓹UIDynamic UI动力学⓺KVC&KVO

    知识点复习   1. 触摸事件&手势识别   1> 4个触摸事件,针对视图的 2> 6个手势识别(除了用代码添加,也可以用Storyboard添加)   附加在某一个特定视图上的, ...

  4. iOS总结_UI层自我复习总结

    UI层复习笔记 在main文件中,UIApplicationMain函数一共做了三件事 根据第三个参数创建了一个应用程序对象 默认写nil,即创建的是UIApplication类型的对象,此对象看成是 ...

  5. vuex复习方案

    这次复习vuex,发现官方vuex2.0的文档写得太简略了,有些看不懂了.然后看了看1.0的文档,感觉很不错.那以后需要复习的话,还是先看1.0的文档吧.

  6. Objective-C之KVC、KVO

    1,KVC(键值编码)  Key Value Coding 1.1在C#中,可以通过字符串反射来获取对象,从而对对象的属性进行读写,Object-C中有同样的实现,通过字符串(属性名词)对对象的属性进 ...

  7. 我的操作系统复习——I/O控制和系统调用

    上篇博客介绍了存储器管理的相关知识——我的操作系统复习——存储器管理,本篇讲设备管理中的I/O控制方式和操作系统中的系统调用. 一.I/O控制方式 I/O就是输入输出,I/O设备指的是输入输出设备和存 ...

  8. KVC 和 KVO

    KVC 键值编码    全称是Key-value coding,翻译成键值编码.它提供了一种使用字符串而不是访问器方法去访问一个对象实例变量的机制.        1.通过key(成员变量的名称)设置 ...

  9. 复习(1)【Maven】

    终于开始复习旧知识了,有输入必然要有输出.输入和输出之间的内化过程尤为重要,在复习的同时,真正把学到的东西积淀下来,加深理解. Maven项目概念与配置 Maven是一个项目管理和综合工具.Maven ...

随机推荐

  1. JPA的基本注解

    场景 JPA入门简介与搭建HelloWorld(附代码下载): https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/103473937 ...

  2. 前端vuex基础入门

    vuex简介 是一个专门为vue.应用程序开的状态管理模式 它采用集中式存储管理应用的所有组件的状态 (类似于全局变量) 并以相应的规则保证以一种可预测的方式发生改变(相应式变化) 应用场景 多个视图 ...

  3. 使用vue在开发中的一些小问题--使用vue-cli起的服务器无法在局域网访问

    2.使用vue-cli起的服务器无法在局域网访问 这个很简单,在package.json文件中的js启动项配置中增加--host 0.0.0.0 注意是--host而不是-host,此时如果有--op ...

  4. Nginx web基础入门

    目录 Nginx web基础入门 如何升级nginx或者添加功能 使用systemd管理nginx nginx相关配置文件 nginx的配置文件详解 日志格式 game日志记录实战 日志切割 手写虚拟 ...

  5. linux 的swap、swappiness及kswapd原理【转】

    本文讨论的 swap基于Linux4.4内核代码 .Linux内存管理是一套非常复杂的系统,而swap只是其中一个很小的处理逻辑. 希望本文能让读者了解Linux对swap的使用大概是什么样子.阅读完 ...

  6. scipy中的coo_matrix函数

    推荐直接看官方文档:https://docs.scipy.org/doc/scipy/reference/generated/scipy.sparse.coo_matrix.html#scipy.sp ...

  7. MYSQL 命令导出事件、存储过程、触发器

    普通导出某个数据库 mysqldump -u username -p passowrd databasename > file.sql 顺便导出事件 使用 –events 参数 mysqldum ...

  8. 【西北师大-2108Java】第九次作业成绩汇总

    [西北师大-2108Java]第九次作业成绩汇总 作业题目 面向对象程序设计(JAVA) 第11周学习指导及要求 实验目的与要求 (1)理解泛型概念: (2)掌握泛型类的定义与使用: (3)掌握泛型方 ...

  9. 【WPF on .NET Core 3.0】 Stylet演示项目 - 简易图书管理系统(2) - 单元测试

    上一章中我们完成了一个简单的登录功能, 这一章主要演示如何对Stylet工程中的ViewModel进行单元测试. 回忆一下我们的登录逻辑,主要有以下4点: 当"用户名"或" ...

  10. vscode源码分析【七】主进程启动消息通信服务

    第一篇: vscode源码分析[一]从源码运行vscode 第二篇:vscode源码分析[二]程序的启动逻辑,第一个窗口是如何创建的 第三篇:vscode源码分析[三]程序的启动逻辑,性能问题的追踪 ...