前言:

KVC又称键值编码(Key-Value-Coding) ,在iOS开发中是一个比较常见的技术点,相信一般开发人员都会使用KVC,其主要的两个方法无非就是设置值和取值,相信也有不少人写UI喜欢使用storyboardxib,他们也经常会使用KVC赋值,如图


今天我们就整体做一下总结

一.KVC机制

附:KVC的官方文档

1.符合使用KVC的对象

翻译总结:

  • 1.对象属性
  • 2.集合(容器)属性
  • 3.集合对象上调用集合运算符
  • 4.访问非对象属性
  • 5.通过键路径访问属性

2.常用方法

以下是一些常用方法演示,在Foundation框架的NSKeyValueCoding中可以去看

获取属性值

@interface NSObject(NSKeyValueCoding)  //分类- (nullable id)valueForKey:(NSString *)key;
//返回由 key 参数命名的属性的值。
//如果根据访问器搜索模式中描述的规则无法找到由键命名的属性,则该对象会向自身发送一条valueForUndefinedKey:消息。的默认实现valueForUndefinedKey:引发NSUndefinedKeyException,但子类可能会覆盖此行为并更优雅地处理这种情况。- (nullable id)valueForKeyPath:(NSString *)keyPath;
// 返回指定密钥路径相对于接收者的值。键路径序列中不符合特定键的键值编码的任何对象(即,默认实现valueForKey:无法找到访问器方法)都会接收valueForUndefinedKey:消息。- (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;
//返回相对于接收器的键数组的值。该方法调用valueForKey:数组中的每个键。返回的NSDictionary包含数组中所有键的值。
笔记:集合对象,如NSArray,NSSet和NSDictionary,不能包含nil的值,可以用NSNull替代

设置属性值

- (void)setValue:(nullable id)value forKey:(NSString *)key;
//将指定键的值相对于接收消息的对象设置为给定值。setValue:forKey:自动解包NSNumber和NSValue表示标量和结构的对象的默认实现并将它们分配给属性。有关包装和展开语义的详细信息,请参阅表示非对象值。
//如果指定的键对应于接收 setter 调用的对象没有的属性,则该对象会向自身发送一条setValue:forUndefinedKey:消息。的默认实现setValue:forUndefinedKey:引发了一个NSUndefinedKeyException. 但是,子类可以覆盖此方法以自定义方式处理请求。- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
//在相对于接收器的指定键路径上设置给定值。键路径序列中不符合特定键的键值编码的任何对象都会收到一条setValue:forUndefinedKey:消息。- (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *, id> *)keyedValues;
//使用指定字典中的值设置接收器的属性,使用字典键来标识属性。默认实现调用setValue:forKey:为每个键-值对,用nil为NSNull对象作为必需的。

关于keyPath

通过keyPath可以进行深层次赋值,例如对象属性的属性,举例说明

    LGPerson *person = [[LGPerson alloc] init];person.student = [[LGStudent alloc]init];[person setValue:@"YCX" forKeyPath:@"student.name"];NSLog(@"取值:%@",[person.student valueForKeyPath:@"name"]);

打印结果

2021-07-31 15:21:00.380946+0800 002-KVC取值&赋值过程[5799:231000] 取值:YCX

3.特殊对象处理

集合属性
如:NSArray或者NSSet等,KVC有着特殊的实现,以下为官方文档的翻译

//符合键值编码的对象以与公开其他属性相同的方式公开其对多属性。您可以像使用valueForKey:and setValue:forKey:(或它们的键路径等价物)的任何其他对象一样获取或设置集合对象。但是,当您想要操作这些集合的内容时,使用协议定义的可变代理方法通常是最有效的。//该协议为集合对象访问定义了三种不同的代理方法,每种方法都有一个键和一个键路径变体:- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;
- (NSMutableArray *)mutableArrayValueForKeyPath:(NSString *)keyPath;
//它们返回一个行为类似于NSMutableArray对象的代理对象。- (NSMutableSet *)mutableSetValueForKey:(NSString *)key;
- (NSMutableSet *)mutableSetValueForKeyPath:(NSString *)keyPath;
//它们返回一个行为类似于NSMutableSet对象的代理对象。- (NSMutableOrderedSet *)mutableOrderedSetValueForKey:(NSString *)key;
- (NSMutableOrderedSet *)mutableOrderedSetValueForKeyPath:(NSString *)keyPath;
//它们返回一个行为类似于NSMutableOrderedSet对象的代理对象。

4.设值顺序

截取了部分设值相关的官方文档

大致意思是:1.搜索名称类似于insertObject:in<Key>AtIndex:and removeObjectFrom<Key>AtIndex:(对应于NSMutableOrderedSet类定义的两个最原始方法)以及insert<Key>:atIndexes:and remove<Key>AtIndexes:(对应于insertObjects:atIndexes:and removeObjectsAtIndexes:)的方法。如果发现至少一个插入方法和至少一种去除方法,返回的代理对象发送的一些组合insertObject:in<Key>AtIndex:,removeObjectFrom<Key>AtIndex:,insert<Key>:atIndexes:,和remove<Key>AtIndexes:消息发送到的原始接收器mutableOrderedSetValueForKey:当它接收到消息NSMutableOrderedSet的消息。代理对象还使用名称类似于replaceObjectIn<Key>AtIndex:withObject:或replace<Key>AtIndexes:with<Key>:当它们存在于原始对象中的方法。2.如果未找到可变集合方法,请搜索名称类似于set<Key>:. 在这种情况下,返回的代理对象每次收到消息时都会set<Key>:向原始接收者发送消息。 mutableOrderedSetValueForKey:NSMutableOrderedSet

这里我们主要得出的验证是设置顺序,以下代码为例

    LGPerson *person = [[LGPerson alloc] init];[person setValue:@"YCX" forKey:@"name"];NSLog(@"%@-%@-%@-%@",person->_name,person->_isName,person->name,person->isName);//LGPerson.m
#pragma mark - 关闭或开启实例变量赋值
+ (BOOL)accessInstanceVariablesDirectly{return YES;
}

通过不断不断调整成员变量和属性的命名,即可验证赋值顺序,总结如下

  • 1.优先通过setter方法,进行属性设置,调用顺序为:setName,_setName,setIsName

  • 2.如果以上方法均未找到,且+ (BOOL)accessInstanceVariablesDirectly方法返回YES,则通过成员变量进行设置,顺序是:_name,_isName,name,isName

  • 3.如果以上方法均未找到,如果+ (BOOL)accessInstanceVariablesDirectly方法返回NO,则直接调用setValue:forUndefinedKey:
    可通过案例进行验证,这里不再展示。

5.流程图

6.取值顺序

截取部分文档内容

参考取值顺序的分析思路,得出结论如下:

  • 1.优先通过getter方法,进行属性取值,调用顺序为:getName,name,isName,_name

  • 2.如果以上方法均未找到,且+ (BOOL)accessInstanceVariablesDirectly方法返回YES,则通过成员变量进行设置,顺序是:_name,_isName,name,isName

  • 3.如果以上方法均未找到,如果+ (BOOL)accessInstanceVariablesDirectly方法返回NO,则直接调用valueForUndefinedKey:
    可通过案例进行验证,这里不再展示。

7.关于valueForUndefinedKey

其实在前面的注释种也有说明,无论是在设值或者是取值时,如果没有找到对应的key,就会调用valueForUndefinedKey:,其实也就是在这里进行报错,也就是我们开发经常遇到的崩溃。
所以如果我们重写这个方法做一些处理就可以防止崩溃

-(id)valueForUndefinedKey:(NSString *)key{NSLog(@"报错,该key不存在%@",key);return nil;
}-(void)setValue:(id)value forUndefinedKey:(NSString *)key{NSLog(@"报错,该key不存在%@",key);
}

包括前面提到的不能传nil,用NSNull代替,也可以在对应的方法做判断

//假设调用-setValue:forKey:将无法设置键控值,因为相应的访问器方法的参数类型是NSNumber标量类型或NSValue结构类型,但值为nil,请使用其他机制设置键控值。此方法的默认实现会引发NSInvalidArgumentException。您可以重写它,将nil值映射到应用程序上下文中有意义的内容。
- (void)setNilValueForKey:(NSString *)key;
- (void)setNilValueForKey:(NSString *)key {if ([key isEqualToString:@"xxx"]) {[self setValue:@"" forKey:@”age”];//其实就是跳过了崩溃的执行} else {[super setNilValueForKey:key];}
}

二.自定义KVC

思路:参考官方文档给的设值和取值规则,以及一些特殊情况的处理,就可以模仿流程去实现自定义KVC,代码如下

@interface NSObject (YCXKVC)// KVC 自定义入口
- (void)ycx_setValue:(nullable id)value forKey:(NSString *)key;
@end
#import "NSObject+YCXKVC.h"
#import <objc/runtime.h>@implementation NSObject (YCXKVC)- (void)ycx_setValue:(nullable id)value forKey:(NSString *)key{// KVC 自定义// 1: 判断什么 keyif (key == nil || key.length == 0) {return;}// 2: setter set<Key>: or _set<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 ycx_performSelectorWithMethodName:setKey value:value]) {NSLog(@"*********%@**********",setKey);return;}else if ([self ycx_performSelectorWithMethodName:_setKey value:value]) {NSLog(@"*********%@**********",_setKey);return;}else if ([self ycx_performSelectorWithMethodName:setIsKey value:value]) {NSLog(@"*********%@**********",setIsKey);return;}// 3: 判断是否响应 accessInstanceVariablesDirectly 返回YES NO 奔溃// 3:判断是否能够直接赋值实例变量if (![self.class accessInstanceVariablesDirectly] ) {@throw [NSException exceptionWithName:@"YCXUnknownKeyException" 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 获取相应的 ivarIvar 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:@"YCXUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ %@]: this class is not key value coding-compliant for the key name.****",self,NSStringFromSelector(_cmd)] userInfo:nil];}- (nullable id)ycx_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:@"YCXUnknownKeyException" 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 -> isNameNSString *_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 @"";
}#pragma mark - 相关方法
- (BOOL)ycx_performSelectorWithMethodName:(NSString *)methodName value:(id)value{if ([self respondsToSelector:NSSelectorFromString(methodName)]) {#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"[self performSelector:NSSelectorFromString(methodName) withObject:value];
#pragma clang diagnostic popreturn YES;}return NO;
}- (id)performSelectorWithMethodName:(NSString *)methodName{if ([self respondsToSelector:NSSelectorFromString(methodName)]) {#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"return [self performSelector:NSSelectorFromString(methodName) ];
#pragma clang diagnostic pop}return nil;
}- (NSMutableArray *)getIvarListName{NSMutableArray *mArray = [NSMutableArray arrayWithCapacity:1];unsigned int count = 0;Ivar *ivars = class_copyIvarList([self class], &count);for (int i = 0; i<count; i++) {Ivar ivar = ivars[i];const char *ivarNameChar = ivar_getName(ivar);NSString *ivarName = [NSString stringWithUTF8String:ivarNameChar];NSLog(@"ivarName == %@",ivarName);[mArray addObject:ivarName];}free(ivars);return mArray;
}@end

今天的分享就到这里,下一篇章KVO原理分析我们不见不散

KVC原理分析及应用相关推荐

  1. KVC/KVO实现原理分析

    2019独角兽企业重金招聘Python工程师标准>>> 1. 函数调用(消息)实现分析: 我们看这条语句: [代码]c#/cpp/oc代码: 1 [self.person setVa ...

  2. java signature 性能_Java常见bean mapper的性能及原理分析

    背景 在分层的代码架构中,层与层之间的对象避免不了要做很多转换.赋值等操作,这些操作重复且繁琐,于是乎催生出很多工具来优雅,高效地完成这个操作,有BeanUtils.BeanCopier.Dozer. ...

  3. Select函数实现原理分析

    转载自 http://blog.chinaunix.net/uid-20643761-id-1594860.html select需要驱动程序的支持,驱动程序实现fops内的poll函数.select ...

  4. spring ioc原理分析

    spring ioc原理分析 spring ioc 的概念 简单工厂方法 spirng ioc实现原理 spring ioc的概念 ioc: 控制反转 将对象的创建由spring管理.比如,我们以前用 ...

  5. 一次 SQL 查询优化原理分析(900W+ 数据,从 17s 到 300ms)

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 来源:Muscleape jianshu.com/p/0768eb ...

  6. 原理分析_变色近视眼镜原理分析

    随着眼镜的发展,眼镜的外型变得越来越好看,并且眼镜的颜色也变得多姿多彩,让佩戴眼镜的你变得越来越时尚.变色近视眼镜就是由此产生的新型眼镜.变色镜可以随着阳光的强弱变换不同的色彩. 变色眼镜的原理分析 ...

  7. jieba分词_从语言模型原理分析如何jieba更细粒度的分词

    jieba分词是作中文分词常用的一种工具,之前也记录过源码及原理学习.但有的时候发现分词的结果并不是自己最想要的.比如分词"重庆邮电大学",使用精确模式+HMM分词结果是[&quo ...

  8. EJB调用原理分析 (飞茂EJB)

    EJB调用原理分析 EJB调用原理分析 作者:robbin (MSN:robbin_fan AT hotmail DOT com) 版权声明:本文严禁转载,如有转载请求,请和作者联系 一个远程对象至少 ...

  9. 深入掌握Java技术 EJB调用原理分析

      深入掌握Java技术 EJB调用原理分析     一个远程对象至少要包括4个class文件:远程对象:远程对象的接口:实现远程接口的对象的stub:对象的skeleton这4个class文件. 在 ...

最新文章

  1. VScode配置prettier和eslint
  2. 管人、管团队、管项目,如何让团队管理者成为技术团队中的发动机?
  3. w ndows7文档加密取消,win7文件夹怎么加密?windows7文件加密方法
  4. python爬取知乎页面的LaTeX公式
  5. 如何修改DynEd的学生记录服务器,DynEd教师管理端操作文档..docx
  6. python写入Excel时,将路径或链接以超链接的方式写入
  7. pythoncopy函数_Python的shutil模块中文件的复制操作函数详解
  8. Bad CPU type in executable
  9. 教你快速将多个TXT文档合并成一个
  10. 1.firefox缺少flash插件
  11. dreamweavercc 数据库_Dreamweaver CC
  12. Python利用 Anaconda安装pytorch并测试GPU
  13. 用Delphi开发微信支付和支付宝支付生成签名并提交刷卡支付请求验证源码
  14. java excel 密码_Java 加密Excel文件(打开时需输入密码)
  15. Openlayers之地图比例尺控件
  16. 简单的总结一下iOS面试中会遇到的问题
  17. 苹果ios免越狱脚本实现方案
  18. Layui数据表格分页通过两种方法实现
  19. 用vscode调试远程服务器的php
  20. 活动回顾|ShardingSphere X openGauss,将会产生怎样的化学反应?

热门文章

  1. 1197:山区建小学
  2. Linux iio驱动学习
  3. 服务器响应图像的生成
  4. PC 网易云音乐桌面歌词原版天际蓝配色方案
  5. html打开txt文件 乱码怎么解决,记事本文件打开是乱码怎么办 乱码如何解决
  6. aria2 txt导入_使用Aria2完成下载任务
  7. 说说数据结构中的几种树
  8. python 画等边三角形
  9. 学校计算机功能室使用登记表,学校各功能室使用情况登记表
  10. 如何对计算机进行磁盘整理,WinXP电脑磁盘碎片怎么整理,教您如何给XP系统电脑进行磁盘碎片整理...