文章目录 一、KVC在iOS中的定义 二、常用的方法说明 三、赋值实现原理 四、取值实现原理
一、KVC在iOS中的定义
KVC的全称为keyValueCoding,简称键值编码。是对NSObjcet的扩展,分类名为 : NSKeyValueCoding
我们经常用KVC或者setter方法来触发KVO,实现键值变化监听,实现一些功能。
二、常用的方法说明
// 1、将键字符串key所对应的属性的值设置为value。不能设定属性值时,将会引起接收器调用方法2 - (void)setValue:(nullable
id)value forKey:(NSString *)key // 2、当属性值设置失败,调用此方法 - (void)setValue:(nullable
id)value forUndefinedKey:(NSString *)key //
3、返回标识属性的键字符串所对应的值。如果获取失败,将会引起接收器调用方法4 - (nullable id)valueForKey:(NSString
*)key// 4、取值失败,调用此方法 - (nullable id)valueForUndefinedKey:(NSString *)key //
5、在键字符串key所对应的"标量"型属性值设为nil,调用此方法,并抛出NSInvalidArgumentException异常(可demo测试) - (
void)setNilValueForKey:(NSString *)key //
6、默认返回值YES,代表如果没有找到Set方法的话,会按照_key,_iskey,key,iskey的顺序搜索成员,设置成NO就不这样搜索 + (BOOL
)accessInstanceVariablesDirectly
标量 : 属性中的单纯的数值(整数、实数、布尔值等)

在赋值的时候
如果是结构体,必须包装成NSValue实例
如果是标量型属性,必须包装成NSNumber实例

三、赋值实现原理
原理总纲 1、查找是否实现setter方法,如果有,优先调用setter方法完成赋值(注意:set后面的键的第一字字母必须是大写!!) 2
、当没找到setter方法,调用accessInstanceVariablesDirectly询问。 如果返回YES,顺序匹配变量名与 _<key>,_is<
Key>,<key>,is<Key>,匹配到则设定其值 如果返回NO,结束查找。并调用 setValue:forUndefinedKey:报异常 3
、如果既没有setter也没有实例变量时,调用 setValue:forUndefinedKey:
结合demo,写下基本实现原理
// .h文件 #import <Foundation/Foundation.h> @interface Peson : NSObject {
//_<key>, _is<Key>, <key>, or is<Key> 注意顺序!!! //NSString *_name; //NSString
*_isName; // NSString *name; NSString *isName; } @end // .m文件 #import "Peson.h"
#import<objc/runtime.h> @implementation Peson - (void)setValue:(id)value
forKey:(NSString *)key { NSString *setter = [[@"set"
stringByAppendingString:[key capitalizedString]] stringByAppendingString:@":"];
// 1、检查是否存在setter方法 if ([self respondsToSelector:NSSelectorFromString(setter)])
{// 1.1 如果是标量型属性赋值,且值为nil,赋值失败 if (![value isKindOfClass:[NSObject class]] &&
value ==nil) { [self setNilValueForKey:key]; // 1.2 如果是对象指针类型,直接进行赋值操作 }else{
#pragma clang diagnostic push #pragma clang diagnostic ignored
"-Warc-performSelector-leaks" [self
performSelector:NSSelectorFromString(setter) withObject:value];#pragma clang
diagnostic pop } // 2、询问 accessInstanceVariablesDirectly,默认YES,继续往下查找 }else{
//获取所有属性列表 unsigned int count = 0; Ivar *ivar = class_copyIvarList([self
class], &count);//_<key>, _is<Key>, <key>, or is<Key> 注意顺序!!! NSArray
*searchPropretys = @[@"_name",@"_isName",@"name",@"isName"]; //
是否找到变量名的标志位,判断是否需要抛出异常 BOOL flag = false; //遍历属性,依次匹配 for (int i = 0; i <
count; i++) {//如果找到了,结束循环 if (flag) { break; }else{ Ivar var = ivar[i]; NSString
*name = [NSString stringWithUTF8String:ivar_getName(var)]; for (int j = 0; j <
searchPropretys.count; j++) { //找到了,结束循环 if ([name
isEqualToString:searchPropretys[j]]) { flag =YES; object_setIvar(self, var,
value);break; } } } } //记得释放 free(ivar); //如果没找到,调用setValue: forUndefinedKey:
抛出异常 if (!flag) { [self setValue:value forUndefinedKey:key]; } } } +(BOOL
)accessInstanceVariablesDirectly {return YES; }
注意: 上面有一个细节需要说下,对于标量型属性赋值,如果是纯数值,需要使用包装类NSNumber,对于结构体,需要用NSValue实例包装。

通过上面我们也可以发现,为什么KVC和setter方法都可以触发KVO 。

四、取值实现原理

取值的原理跟赋值原理差不多,也写下吧
原理总纲 1、查找是否实现getter方法,依次匹配`-get<Key>` 和 `-<key>` 和 `is<Key>`,如果找到,直接返回。 需要注意的是
: 如果返回的是对象指针类型,则返回结果。 如果返回的是NSNumber转换所支持的标量类型之一,则返回一个NSNumber 否则,将返回一个NSValue2
、当没有找到getter方法,调用accessInstanceVariablesDirectly询问 如果返回YES, _<key>,_is<Key>,<key
>,is<Key>,找到了返回对应的值 如果返回NO,结束查找。并调用 valueForUndefinedKey: 报异常 3
、如果没找到getter方法和属性值,调用 valueForUndefinedKey: 报异常
// 进阶着实现上面demo的取值方法
- (id)valueForKey:(NSString *)key { // 1. 查找getter方法 // -get<Key> NSString
*getKey = [@"get" stringByAppendingString:[key capitalizedString]]; NSString
*isKey = [@"is" stringByAppendingString:[key capitalizedString]]; // 1.1 优先查找
-get<Key> if ([self respondsToSelector:NSSelectorFromString(getKey)]) { #pragma
clang diagnostic push #pragma clang diagnostic ignored
"-Warc-performSelector-leaks" [self
performSelector:NSSelectorFromString(getKey)];#pragma clang diagnostic pop //
1.2 查找 -<key> }else if ([self respondsToSelector:NSSelectorFromString(key)]) {
#pragma clang diagnostic push #pragma clang diagnostic ignored
"-Warc-performSelector-leaks" [self performSelector:NSSelectorFromString(key)];
#pragma clang diagnostic pop // 1.3 查找 is<Key> }else if ([self
respondsToSelector:NSSelectorFromString(isKey)]) {#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks" [self
performSelector:NSSelectorFromString(isKey)];#pragma clang diagnostic pop // 2.
询问 accessInstanceVariablesDirectly,是否继续查找属性,默认返回YES }else{ //_<key>, _is<Key>,
<key>, or is<Key> 注意顺序!!! //获取所有属性列表 unsigned int count = 0; Ivar *ivar =
class_copyIvarList([self class], &count); //_<key>, _is<Key>, <key>, or is<Key>
注意顺序!!! NSArray *searchPropretys = @[@"_name",@"_isName",@"name",@"isName"];
BOOL flag = false; //遍历属性,依次匹配 for (int i = 0; i < count; i++) { //如果找到了,跳出外重循环
if (flag) { break; }else{ Ivar var = ivar[i]; NSString *name = [NSString
stringWithUTF8String:ivar_getName(var)];for (int j = 0; j < searchPropretys
.count; j++) { //找到了,结束循环 if ([name isEqualToString:searchPropretys[j]]) { flag
=YES; return object_getIvar(self, var); break; } } } } free(ivar); //如果没找到,调用 if
(!flag) { [self valueForUndefinedKey:key]; } } return nil; }
工程放这里了,有兴趣的小伙伴可以下载研究下 <https://download.csdn.net/download/qq_18505715/10394645>

若一个类有实例变量NSString *_foo,调用setValue: forKey: 时,可以以foo还是_foo作为key?

看完这篇博客应该知道怎么回答了吧。

友情链接
KaDraw流程图
API参考文档
OK工具箱
云服务器优惠
阿里云优惠券
腾讯云优惠券
华为云优惠券
站点信息
问题反馈
邮箱:[email protected]
QQ群:637538335
关注微信