KVC : 键值编码, 是一种可以通过字符串的名字 (Key) 来访问类当中的方法,属性等. 通过查阅 NSKeyValueCoding.h 可以知道, apple 对 NSObject 类进行了扩展,以此来实现我们用到的 '-setValue:forKey:' 和 'valueForKey:key' 等方法.
KVC 当中的一些我接下来有用到的方法声明:
(英语不好, 一些直接是翻译软件翻的, 有错的希望纠正下)
/*
根据一个标识属性或一对一关系的键, 返回对应的属性值或相关对象
给定一个确定对应许多关系的键, 返回一个包含了所有相关对象的不可变数组或者不可变集合
*/
- (nullable id)valueForKey:(NSString *)key;
/*
给定一个值和一个标识属性的键, 设置属性的值.
给定一个对象和一个标识一对一关系的键, 将该对象与接收方联系起来, 如果有一个对象, 则将先前相关的对象关联起来.
给定一个集合对象和一个标识一对多关系的键, 将集合中包含的对象与接收方关联起来,如果有任何关系, 则将之前相关的对象关联起来
*/
- (void)setValue:(nullable id)value forKey:(NSString *)key;
/*
当对应的访问器方法是值为 nil 的 NSNumber 或者 NSValue 类型时, 调用 '-setValue:forKey:' 将无法设置相关键属性的值.
该方法默认抛出一个 NSInvalidArgumentException . 你可以重写此方法做你想做的处理.
*/
- (void)setNilValueForKey:(NSString *)key;
/*
如果调用 '-setValue:forKey:' 无法通过其默认访问机制获得key.
那么将会调用此方法, 该方法的默认实现了一个NSUndefinedKeyException. 你可以重写此方法做你想做的处理.
*/
- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;
/*
如果调用 '-valueForKey:' 无法通过其默认访问机制获得key.
那么将会调用此方法, 该方法的默认实现了一个NSUndefinedKeyException. 你可以重写此方法做你想做的处理.
*/
- (nullable id)valueForUndefinedKey:(NSString *)key;
KVC实现原理
我这里首先简单列举一下 '-setValue:forKey:' 的查找机制:
- 1 在接收方的类中搜索一个访问器方法, 该方法的名称为 -set
:.如果找到这样的方法, 则检查其参数的类型. 如果参数类型不是一个对象指针类型但值为nil, -setNilValueForKey: 将被调用. 否则, 如果方法的参数类型是一个对象指针类型则该方法就被简单地用值作为参数调用. 如果方法的参数类型是另一种类型, 则是在调用方法之前执行由 -valueForKey: 执行的 NSNumber/NSValue 转换的逆函数; - 2 否则(没有找到访问器指定方法), 如果接收方的类 +accessInstanceVariablesDirectly 属性返回YES, 这种顺序搜索则接收者的类中按 _
, _is , , 或者 is 这种顺序搜索一个实例变量名称与之相匹配的. 如果找到了这样一个实例变量, 并且它的类型是一个对象指针类型, 那么该值将被保留, 并且在实例变量的旧值被首次释放后, 其结果被设置在实例变量中. 如果实例变量的类型是另一种类型, 那么它的值是设置为与第1步的NSNumber或NSValue转换为类型之后相同的类型 - 3 否则(没有找到访问器方法或实例变量),调用 -setValue:forUndefinedKey:.
这里可以通过例子测试下
写一个Person类:
//首先来验证第一步的步骤
@interface Person ()
{
NSString *isName;
}
@property (readwrite, nonatomic, copy) NSString *name;
@property (readwrite, nonatomic, assign) NSUInteger age;
@end
@implementation Person
- (void)setName:(NSString *)name{
NSLog(@"-setName:, name = %@",name);
_name = name;
}
- (void)setAge:(NSUInteger)age{
NSLog(@"-setAge:, age = %ld",age);
_age = age;
}
- (id)valueForUndefinedKey:(NSString *)key{
NSLog(@"-valueForUndefinedKey:, key = %@",key);
return nil;
}
- (void)setNilValueForKey:(NSString *)key{
NSLog(@"-setNilValueForKey:, key = %@",key);
}
- (void)setValue:(id)value forUndefinedKey:(NSString *)key{
NSLog(@"-setValue:forUndefinedKey:, value = %@, key = %@",value,key);
}
@end
Step 1.
是否正常查找 -set
//测试代码
Person *p = [[Person alloc]init];
[p setValue:@"nearMilk" forKey:@"name"];
//输出结果
2017-08-04 13:14:36.147 CopyNSKeyValueCoding[1417:1045537] -setName:, name = nearMilk
当方法参数类型为非指针类型时值为 nil
[p setValue:nil forKey:@"age"];
//输出结果
2017-08-04 13:28:58.021 CopyNSKeyValueCoding[1458:1101273] -setNilValueForKey:, key = age
//正常情况下
[p setValue:@(23) forKey:@"age"];//这里你在set值的时候已经进行装箱工作, 基本数据类型转换为NSNumber, 结构体转换为NSValue.
//输出结果
2017-08-04 13:33:39.177 CopyNSKeyValueCoding[1485:1128474] -setAge:, age = 23
首先因为看说明也可以知道搜索的是 -set
Step 2.
首先我们可以先重写 +accessInstanceVariablesDirectly 方法为 NO 试试
…… 我把相关的属性和 setter 方法注释掉了
//@property (readwrite, nonatomic, copy) NSString *name;
//@property (readwrite, nonatomic, assign) NSUInteger age;
……
+ (BOOL)accessInstanceVariablesDirectly{
return NO;
}
//- (void)setName:(NSString *)name{
// NSLog(@"-setName:, name = %@",name);
// _name = name;
//}
//- (void)setAge:(NSUInteger)age{
// NSLog(@"-setAge:, age = %ld",age);
// _age = age;
//}
……
然后我们来重新输入代码
[p setValue:@"nearMilk" forKey:@"name"];
//输出结果
2017-08-04 13:44:12.371 CopyNSKeyValueCoding[1566:1177255] -setValue:forUndefinedKey:, value = nearMilk, key = name
对的, 直接走了 -setValue:forUndefinedKey: 方法, 我们把 +accessInstanceVariablesDirectly 重新设置为 YES 或者删掉这个方法, 然后写以下代码:
NSLog(@"name = %@",[p valueForKey:@"name"]);
[p setValue:@"nearMilk" forKey:@"name"];
NSLog(@"name = %@",[p valueForKey:@"name"]);
//输出结果
2017-08-04 13:48:50.306 CopyNSKeyValueCoding[1600:1197103] name = (null)
2017-08-04 13:48:50.315 CopyNSKeyValueCoding[1600:1197103] name = nearMilk
没错, 我已经把 setter 方法注释了, 它还是设置成功了, 因为我声明了个实例变量 isName, 这个是符合系统的查找机制的, 所以它能找到这个变量并且对它进行赋值.
Step 3.
当按步骤一和步骤二都没有找到相匹配的方法或者实例变量时, 直接调用 -setValue:forUndefinedKey:
…… 注释实例变量
// NSString *isName;
……
[p setValue:@"nearMilk" forKey:@"name"];
//输出结果
2017-08-04 13:56:22.640 CopyNSKeyValueCoding[1652:1230312] -setValue:forUndefinedKey:, value = nearMilk, key = name
接着简单列举一下 ‘-valueForKey:’ 的查找机制:
1 在接收方的类中搜索名称与模式的名称匹配的, 即按 -get
, - , 或者 -is 这个顺序. 如果找到了这样的方法, 就会调用它. 如果方法的结果类型是对象指针类型, 结果就会返回. 如果结果的类型是NSNumber转换所支持的标量类型之一,则返回一个NSNumber. 否则, 就会进行转换, 并返回一个NSValue(新的Mac OS 10.5:任意类型的结果被转换为NSValues,不只是NSPoint、NRange、NSRect和NSSize); 2 (在Mac OS 10.7中引入)否则(不存在简单的访问器方法), 在接收方的类中搜索名称与模式的名称匹配的, -countOf
, -indexIn OfObject:, -objectIn AtIndex:(与NSOrderedSet类的原始方法相对应)以及 - AtIndexes:(对应于-[NSOrderedSet objectsAtIndexes:]). 如果找到一个count方法和一个indexOf方法, 以及其他两个可能的方法中的一个, 那么将返回一个响应所有NSOrderedSet方法的集合代理对象.发送到集合代理对象的每个NSOrderedSet消息都会导致一些消息-countOf , -indexIn OfObject:, -objectIn AtIndex:, 和 - AtIndexes:被发送到 -valueForKey:的原始接收者.如果接收方的类也实现了一个可选的方法, 它的名称与模式相匹配 -get :range: 将在适合最佳性能时使用; 3 否则(没有一个简单的访问方法或一组有序的访问方法), 在接收方的类中搜索其名称与模式匹配的方法 -countOf
和 -objectIn AtIndex: (对应于NSArray类定义的原始方法)和(在Mac OS 10.4中引入)还有 - AtIndexes: (对应于 —[NSArray objectsAtIndexes:]). 如果找到一个count方法和其他两种可能的方法中的一个, 那么将返回一个响应所有NSArray方法的集合代理对象.发送到集合代理对象的每个NSArray消息都会导致一些消息 -countOf , -objectIn AtIndex:, 和 - AtIndexes: 被发送到 -valueForKey:的原始接收者. 如果接收方的类也实现了一个可选的方法, 与它的名称匹配的 -get :range: 方法将在适合最佳性能时使用; 4 (在Mac OS 10.4中引入)否则(没有一个简单的存取器方法或一组有序集或数组访问方法),搜索接收者类的三个方法, 这些方法的名称按 -countOf
, -enumeratorOf , and -memberOf : 匹配(对应于NSSet类定义的原始方法). 如果找到所有三个这样的方法, 那么将返回一个响应所有NSSet方法的集合代理对象. 发送到集合代理对象的每个NSSet消息都会导致一些消息 -countOf , -enumeratorOf , 和 -memberOf : 被发送到 -valueForKey: 的原始接收者; 5 否则(不存在简单的访问方法或集合访问方法集), 如果接收者的类 + accessInstanceVariablesDirectly 属性返回YES,搜索接收机的类的一个实例变量名称这个顺序与 _
, _is , , 或者 is 匹配. 如果找到了这样一个实例变量, 那么返回的实例变量的值将返回, 与第1步中的NSNumber或NSValue的转换相同; 6 否则(不存在简单的访问方法、集合访问方法集或实例变量), 调用 -valueForUndefinedKey: 并返回.
因为步骤2, 3, 4 太烦而且我也不太明白就不写了, 这里可以去这里看看他们的验证
重写 Person 类
@interface Person ()
{
NSString *isName;
}
@end
@implementation Person
//+ (BOOL)accessInstanceVariablesDirectly{
// return NO;
//}
- (NSString *)getName{
NSLog(@"-getName");
return @"nearMilk";
}
- (NSUInteger)getAge{
NSLog(@"-getAge");
return 23;
}
- (id)valueForUndefinedKey:(NSString *)key{
NSLog(@"-valueForUndefinedKey:, key = %@",key);
return nil;
}
@end
Step 1.
是否走了 -get
Person *p = [[Person alloc]init];
[p valueForKey:@"name"];
[p valueForKey:@"age"]; 在这里返回的age其实已经被转换为 NSNumber
//输出结果
2017-08-04 14:18:30.399 CopyNSKeyValueCoding[1719:1344515] -getName
2017-08-04 14:18:30.399 CopyNSKeyValueCoding[1719:1344515] -getAge
跳过 2, 3, 4...
Step 5.
设置 +accessInstanceVariablesDirectly 为 NO, 注释 getter 方法
……
+ (BOOL)accessInstanceVariablesDirectly{
return NO;
}
//- (NSString *)getName{
// NSLog(@"-getName");
// return @"nearMilk";
//}
//
//- (NSUInteger)getAge{
// NSLog(@"-getAge");
// return 23;
//}
重新输入测试代码
Person *p = [[Person alloc]init];
[p valueForKey:@"name"];
[p valueForKey:@"age"];
//输出结果
2017-08-04 14:34:58.803 CopyNSKeyValueCoding[1815:1405306] -valueForUndefinedKey:, key = name
2017-08-04 14:34:58.803 CopyNSKeyValueCoding[1815:1405306] -valueForUndefinedKey:, key = age
把 +accessInstanceVariablesDirectly 恢复后测试
Person *p = [[Person alloc]init];
NSLog(@"name = %@",[p valueForKey:@"name"]);
[p setValue:@"nearMilk" forKey:@"name"];
NSLog(@"name = %@",[p valueForKey:@"name"]);
//输出结果
2017-08-04 14:38:38.253 CopyNSKeyValueCoding[1859:1428334] name = (null)
2017-08-04 14:38:38.253 CopyNSKeyValueCoding[1859:1428334] name = nearMilk
正常打印出结果来了, 因为我们有一个 isName 实例变量, 按照机制直接对它的值进行 set, get.
Step 6.
我们把 isName 实例变量也去掉后, 输出结果如下:
/*测试代码
Person *p = [[Person alloc]init];
[p valueForKey:@"name"];
*/
2017-08-04 14:40:42.775 CopyNSKeyValueCoding[1882:1434832] -valueForUndefinedKey:, key = name
以上就是 KVC 当中两个存取方法的原理, 其它的我也没研究.
KVC的内部实现
知道原理以后我们就应该可以自己手动去实现一个 -setValue:forKey 和 -valueForKey ,以及它的3个错误方法了.我这里对实例变量类型为非指针对象的存取值没找到怎么解决就直接用//code ,//endcode 隔开来了.
//在不考虑可能多层调用导致增加耗时的情况下
//将key转换为set方法名称
- (NSString *)convertToSetWithKey:(NSString *)key{
NSString *setKey = [NSString stringWithFormat:@"set%@:",[self convertLetterCapitalized:key]];
return setKey;
}
//将key转换为一组符合要求的名字来查找实例变量
- (NSArray *)convertKeyToInstanceVariable:(NSString *)key{
NSString *_key = [NSString stringWithFormat:@"_%@",key];
NSString *_isKey = [NSString stringWithFormat:@"_is%@",[self convertLetterCapitalized:key]];
NSString *isKey = [NSString stringWithFormat:@"is%@",[self convertLetterCapitalized:key]];
NSArray *array = @[_key,_isKey,key,isKey];
return array;
}
//将key转换为一组符合valueForKey步骤一要求的名字来查找实例方法
- (NSArray *)convertGetKeyToStepOneInstanceMethod:(NSString *)key{
NSString *getKey = [NSString stringWithFormat:@"get%@",[self convertLetterCapitalized:key]];
NSString *isKey = [NSString stringWithFormat:@"is%@",[self convertLetterCapitalized:key]];
NSArray *array = @[getKey,key,isKey];
return array;
}
- (NSString *)convertLetterCapitalized:(NSString *)key{
NSString *firstLetter = [key substringToIndex:1].uppercaseString;//首字母大写
NSString *remainingLetters = [key substringFromIndex:1];
return [NSString stringWithFormat:@"%@%@",firstLetter,remainingLetters];
}
/** 将value拆箱调用
* @param type value的类型
* @param value 一个传入值
* @param sel a sel
*/
//这里转换有点问题,还不知道如何动态转换NSValue的objcType的
- (void)splitOpenCase:(char *)type value:(id)value sel:(SEL)sel{
//这里用的指定基本数据类型,当符合 -set:,但是参数类型与原本属性数据类型不同的话这边发送消息不知道会发生什么.
switch (*type) {
case 'c':{
char aChar = [value charValue];
void(*cp_objc_msgSend)(id,SEL,char) = (void *)objc_msgSend;
cp_objc_msgSend(self,sel,aChar); return ;
}
case 'C':
{
unsigned char unsignedChar = (unsigned char)[value charValue];
void(*cp_objc_msgSend)(id,SEL,unsigned char) = (void *)objc_msgSend;
cp_objc_msgSend(self,sel,unsignedChar); return ;
}
case 's':
{
short aShort = [value shortValue];
void(*cp_objc_msgSend)(id,SEL,short) = (void *)objc_msgSend;
cp_objc_msgSend(self,sel,aShort); return ;
}
case 'i':
{
int aInt = [value intValue];
void(*cp_objc_msgSend)(id,SEL,int) = (void *)objc_msgSend;
cp_objc_msgSend(self,sel,aInt); return ;
}
case 'I':
{
unsigned int unsignedInt = (unsigned int)[value intValue];
void(*cp_objc_msgSend)(id,SEL,unsigned int) = (void *)objc_msgSend;
cp_objc_msgSend(self,sel,unsignedInt); return ;
}
//这里我不太知道long 和long long的在这里的关系我用long先
case 'q':
{
long aLong= [value longValue];
void(*cp_objc_msgSend)(id,SEL,long) = (void *)objc_msgSend;
cp_objc_msgSend(self,sel,aLong); return ;
}
case 'Q':
{
unsigned long unsignedLong = (unsigned long)[value longValue];
void(*cp_objc_msgSend)(id,SEL,unsigned long) = (void *)objc_msgSend;
cp_objc_msgSend(self,sel,unsignedLong); return ;
}
case 'f':
{
float aFloat = [value floatValue];
void(*cp_objc_msgSend)(id,SEL,float) = (void *)objc_msgSend;
cp_objc_msgSend(self,sel,aFloat); return ;
}
case 'd':
{
double aDouble = [value doubleValue];
void(*cp_objc_msgSend)(id,SEL,double) = (void *)objc_msgSend;
cp_objc_msgSend(self,sel,aDouble); return ;
}
case 'B':
{
BOOL aBool = [value boolValue];
void(*cp_objc_msgSend)(id,SEL,BOOL) = (void *)objc_msgSend;
cp_objc_msgSend(self,sel,aBool); return ;
}
default:{
//NSValue
// CGRect rect;
// NSValue *avalue = (NSValue *)value;
// NSLog(@"%s",avalue.objCType);
// [value getValue:&rect];
// void(*cp_objc_msgSend)(id,SEL,CGRect) = (void *)objc_msgSend;
// cp_objc_msgSend(self,sel,rect); return ;
}
}
}
/** 返回一个NSNumber类型
* @param type value的类型
* @param sel a sel
* @return 返回一个装箱的NSNumber,不可转换的返回nil
*/
- (NSNumber *)packing:(char *)type sel:(SEL)sel{
NSNumber *value = nil;
switch (*type) {
case 'c':{
char (*cp_objc_msgSend)(id,SEL) = (void *)objc_msgSend;
value = [NSNumber numberWithChar:cp_objc_msgSend(self,sel)];
}
break;
case 'C':
{
unsigned char (*cp_objc_msgSend)(id,SEL) = (void *)objc_msgSend;
value = [NSNumber numberWithUnsignedChar:cp_objc_msgSend(self,sel)];
}
break;
case 's':
{
short (*cp_objc_msgSend)(id,SEL) = (void *)objc_msgSend;
value = [NSNumber numberWithShort:cp_objc_msgSend(self,sel)];
}
break;
case 'i':
{
int (*cp_objc_msgSend)(id,SEL) = (void *)objc_msgSend;
value = [NSNumber numberWithInt:cp_objc_msgSend(self,sel)];
}
break;
case 'I':
{
unsigned int (*cp_objc_msgSend)(id,SEL) = (void *)objc_msgSend;
value = [NSNumber numberWithUnsignedInt:cp_objc_msgSend(self,sel)];
}
break;
//这里我不太知道long 和long long的在这里的关系我用long先
case 'q':
{
long (*cp_objc_msgSend)(id,SEL) = (void *)objc_msgSend;
value = [NSNumber numberWithInteger:cp_objc_msgSend(self,sel)];
}
break;
case 'Q':
{
unsigned long (*cp_objc_msgSend)(id,SEL) = (void *)objc_msgSend;
value = [NSNumber numberWithUnsignedInteger:cp_objc_msgSend(self,sel)];
}
break;
case 'f':
{
float (*cp_objc_msgSend)(id,SEL) = (void *)objc_msgSend;
value = [NSNumber numberWithFloat:cp_objc_msgSend(self,sel)];
}
break;
case 'd':
{
double (*cp_objc_msgSend)(id,SEL) = (void *)objc_msgSend;
value = [NSNumber numberWithDouble:cp_objc_msgSend(self,sel)];
}
break;
case 'B':
{
BOOL (*cp_objc_msgSend)(id,SEL) = (void *)objc_msgSend;
value = [NSNumber numberWithBool:cp_objc_msgSend(self,sel)];
}
break;
default:
break;
}
return value;
}
- (void)cp_setValue:(id)value forKey:(NSString *)key{
//检查key是否为空字符串
if (key == nil || key.length <= 0) {
[self cp_setValue:value forUndefinedKey:key];
}
//获取当前实例的类
//第一步:搜索类中是否有 -set: 的方法.如果有此方法,检查对象是否为一个对象指针类型,不是的话参数类型不能为nil,否则调用 -cp_setNilValueForKey:.将非指针对象的参数用 NSNumber/NSValue转换,调用此方法;否则执行第二步.
Class clazz = object_getClass(self);
SEL setterSEL = NSSelectorFromString([self convertToSetWithKey:key]);
Method setterMethod = class_getInstanceMethod(clazz, setterSEL);
if (setterMethod) {
//检查方法,系统的好像即使返回类型不是void的是其它返回类型只要满足 -set也会调用,下面的同理.所以在这里我只检查后面的参数类型(好像只要不是@的在设置为nil的时候都调用 -cp_setNilValueForKey:).
char *type = method_copyArgumentType(setterMethod, 2);
if (*type != '@') {
if (value == nil) {
[self cp_setNilValueForKey:key]; return;
}
[self splitOpenCase:type value:value sel:setterSEL];
return;
}
void(*cp_objc_msgSend)(id,SEL,id) = (void *)objc_msgSend;
cp_objc_msgSend(self,setterSEL,value);
}else{
//第二步:如果accessInstanceVariablesDirectly为NO,执行 -cp_setValue:forUndefinedKey:,YES按_, _is, , 或者 is相匹配的实例变量查找,否则进行第三步.
if (![clazz accessInstanceVariablesDirectly]) {
[self cp_setValue:value forUndefinedKey:key]; return;
}
unsigned int ivarCount = 0;
//返回类中所有实例变量
Ivar *ivars = class_copyIvarList(clazz, &ivarCount);
//返回符合要求名字的字符串数组
NSArray *keyNameArray = [self convertKeyToInstanceVariable:key];
//遍历ivars数组中再跟符合要求名字的数组中元素一一比对
for (unsigned int i = 0; i < ivarCount; i++) {
const char *ivarCName = ivar_getName(ivars[i]);
NSString *ivarName = [NSString stringWithCString:ivarCName encoding:NSUTF8StringEncoding];
for (NSUInteger j = 0; j < keyNameArray.count; j++) {
if ([ivarName isEqualToString:keyNameArray[j]]) {
//找到符合要求的ivar,根据对象类型进行相应操作
const char *ivarType = ivar_getTypeEncoding(ivars[i]);
if (*ivarType != '@') {
if (value == nil) {
free(ivars); ivars = NULL;
[self cp_setNilValueForKey:key];
return;
}
//code
//endcode
}
object_setIvar(self, ivars[i], value);
free(ivars); ivars = NULL; return;
}
}
}
//第三步:相匹配的实例变量也找不到,调用 -cp_setValue:forUndefinedKey:.
free(ivars); ivars = NULL;
[self cp_setValue:value forUndefinedKey:key];
}
}
//这个方法的步骤有点多,六步骤
- (id)cp_valueForKey:(NSString *)key{
//检查key是否为空字符串
if (key == nil || key.length <= 0) {
[self cp_valueForUndefinedKey:key];
}
//获取当前实例的类
//第一步:搜索类中是否有 -set: 的方法.如果有此方法,检查对象是否为一个对象指针类型,不是的话参数类型不能为nil,否则调用 -cp_setNilValueForKey:.将非指针对象的参数用 NSNumber/NSValue转换,调用此方法;否则执行第二步.
Class clazz = object_getClass(self);
unsigned int methodCount = 0;
Method *methods = class_copyMethodList(clazz, &methodCount);
if (methods) {
for (unsigned int i = 0; i < methodCount; i++) {
SEL methodSel = method_getName(methods[i]);
NSString *methodName = NSStringFromSelector(methodSel);
//步骤一:查找符合-get,-,或者 -is的,判断其方法结果的类型做相应操作
NSArray *stepOneArray = [self convertGetKeyToStepOneInstanceMethod:key];
for (NSUInteger stepOne = 0; stepOne < stepOneArray.count; stepOne++) {
if ([methodName isEqualToString:stepOneArray[stepOne]]) {
char *returnType = method_copyReturnType(methods[i]);
NSNumber *value = [self packing:returnType sel:methodSel];
if (!value) {
//如果为结构体
}
return value;
}
}
}
//步骤五:判断accessInstanceVariablesDirectly值,然后做是否搜寻实例变量操作
if (![self.class cp_accessInstanceVariableDirectly]) {
[self cp_valueForUndefinedKey:key];
}
unsigned int ivarCount = 0;
//返回类中所有实例变量
Ivar *ivars = class_copyIvarList(clazz, &ivarCount);
//返回符合要求名字的字符串数组
NSArray *keyNameArray = [self convertKeyToInstanceVariable:key];
//遍历ivars数组中再跟符合要求名字的数组中元素一一比对
for (unsigned int i = 0; i < ivarCount; i++) {
const char *ivarCName = ivar_getName(ivars[i]);
NSString *ivarName = [NSString stringWithCString:ivarCName encoding:NSUTF8StringEncoding];
for (NSUInteger j = 0; j < keyNameArray.count; j++) {
if ([ivarName isEqualToString:keyNameArray[j]]) {
//找到符合要求的ivar,根据对象类型进行相应操作
const char *ivarType = ivar_getTypeEncoding(ivars[i]);
if (*ivarType != '@') {
//code
//endcode
}
//指针对象
id ivarValue = object_getIvar(self, ivars[i]);
free(ivars); ivars = NULL;
return ivarValue;
}
}
}
free(ivars); ivars = NULL;
[self cp_valueForUndefinedKey:key];
}
return nil;
}
- (void)cp_setValue:(id)value forUndefinedKey:(NSString *)key{
NSString *reasoon = [NSString stringWithFormat:@"[%@ cp_setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key %@.",[self description],key];
@throw [NSException exceptionWithName:NSUndefinedKeyException reason:reasoon userInfo:nil];
}
- (id)cp_valueForUndefinedKey:(NSString *)key{
[self cp_setValue:nil forUndefinedKey:key]; return nil;
}
- (void)cp_setNilValueForKey:(NSString *)key{
NSString *reason = [NSString stringWithFormat:@"[%@ cp_setNilValueForKey]: could not set nil as the value for the key %@.",[self description],key];
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:reason userInfo:nil];
}
@end
太懒了, 直接把代码贴出去来了, 然后一些代码感觉写的太烦了, 如果有什么优雅的写法也告诉我, 我也好学习学习.
总结
以上也是我自己学习KVC的时候看的一些博客然后自己做的会做的测试写出来了, 然后一些官方文档的上面也说了都是翻译软件翻的, 希望能纠出来一下让我也好好再去理解一下.这篇文章就到这里了.这是我的github.上面的源码在这里, 测试代码没写什么的就莫怪了哈.