简介
- KVC全程Key-Value Coding
键值编码是NSKeyValueCoding非正式协议支持的一种机制,对象采用这种非正式协议来提供对其属性的间接访问。当一个对象的键值编码是兼容的,它的属性可以通过一个简洁、统一的消息传递接口通过字符串参数寻址。这种间接访问机制补充了实例变量及其关联访问器方法提供的直接访问。
常用API
- (void)setValue:(nullable id)value forKey:(NSString *)key;
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
- (nullable id)valueForKey:(NSString *)key;
- (nullable id)valueForKeyPath:(NSString *)keyPath;
原理
- 通过代码跟踪发现KVC相关API在Foundation框架中,而Foundation又不开源,所以我们只能通过反汇编获取官方文档的方式进行探索,下面我们通过官网文档探索KVC文档地址
取值原理
- 1.首先查找get方法,查找顺序get<Key>, <key>, is<Key>, or _<key>,找到的话直接返回结果
- 2.如果get方法没找到,会查找countOf <Key>和objectIn <Key> AtIndex :和<key> AtIndexes :
- 如果找到countOf <Key>和其他两个中的一个,则会创建一个响应所有NSArray方法的集合代理对象,并返回该对象,即NSKeyValueArray,是NSArray的子类。代理对象随后将接收到的所有NSArray消息转换为countOf<Key>,objectIn<Key> AtIndex:和<key>AtIndexes:消息的某种组合,用来创建键值编码对象。如果原始对象还实现了一个名为get<Key>:range:之类的可选方法,则代理对象也将在适当时使用该方法(注意:方法名的命名规则要符合KVC的标准命名方法,包括方法签名。)
- 3.如果没有找到上面的方法,则会同时查找countOf <Key> enumeratorOf<Key>和memberOf<Key>这三个方法,如果这三个方法都找到,则会创建一个响应所有NSSet方法的集合代理对象,并返回该对象,此代理对象随后将其收到的所有NSSet消息转换为countOf<Key>,enumeratorOf<Key>和memberOf<Key>:消息的某种组合,用于创建它的对象
- 4.这一步会先判断 accessInstanceVariablesDirectly是否返回YES,如果返回YES,继续往下查找,如果为NO,直接报错信息
- 5.依次查找成员变量_<key>, _is<Key>, <key>, or is<Key>,找到的话返回结果,没找到报错信息
设值原理
- 1.首先依次查找set方法 set<Key>:-> _set<Key> -> setIs<Key>
- 2.判断accessInstanceVariablesDirectly是否返回YES,如果返回YES,继续依次查找成员变量_<key> -> _is<Key> -> <key> -> is<Key>,如果为NO直接报错信息
自定义KVC
#import "NSObject+YX_KVC.h"
#import <objc/runtime.h>
@implementation NSObject (YX_KVC)
- (id)yx_valueForKey:(NSString *)key
{
if (key == nil || key.length == 0) {
return nil;
}
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
if (![[self class] accessInstanceVariablesDirectly]) {
@throw [NSException exceptionWithName:@"YXUnknowKey" reason:[NSString stringWithFormat:@"找不到%@的相关get方法",key] userInfo:nil];
}
NSMutableArray *ivarNameArray = [self getIvarsNameArray];
NSString *_key = [NSString stringWithFormat:@"_%@",key];
NSString *_isKey = [NSString stringWithFormat:@"_is%@",Key];
NSString *isKey = [NSString stringWithFormat:@"is%@",Key];
if ([ivarNameArray containsObject:_key]) {
Ivar ivar = class_getInstanceVariable([self class], _key.UTF8String);
return object_getIvar(self, ivar);
}
if ([ivarNameArray containsObject:_isKey]) {
Ivar ivar = class_getInstanceVariable([self class], _isKey.UTF8String);
return object_getIvar(self, ivar);
}
if ([ivarNameArray containsObject:key]) {
Ivar ivar = class_getInstanceVariable([self class], key.UTF8String);
return object_getIvar(self, ivar);
}
if ([ivarNameArray containsObject:isKey]) {
Ivar ivar = class_getInstanceVariable([self class], isKey.UTF8String);
return object_getIvar(self, ivar);
}
return @"";
}
- (void)yx_setValue:(id)value forKey:(NSString *)key
{
if (key == nil || key.length == 0) {
return;
}
NSString *Key = key.capitalizedString;
NSString *setKey = [NSString stringWithFormat:@"set%@",Key];
NSString *_setKey = [NSString stringWithFormat:@"_set%@",Key];
NSString *setIsKey = [NSString stringWithFormat:@"setIs%@",Key];
if ([self yx_performSelectorWithSelName:setKey value:value]) {
return;
}
if ([self yx_performSelectorWithSelName:_setKey value:value]) {
return;
}
if ([self yx_performSelectorWithSelName:setIsKey value:value]) {
return;
}
if (![[self class] accessInstanceVariablesDirectly]) {
@throw [NSException exceptionWithName:@"YXUnknowKey" reason:[NSString stringWithFormat:@"找不到%@的相关set方法",key] userInfo:nil];
}
NSMutableArray *ivarNameArray = [self getIvarsNameArray];
NSString *_key = [NSString stringWithFormat:@"_%@",key];
NSString *_isKey = [NSString stringWithFormat:@"_is%@",Key];
NSString *isKey = [NSString stringWithFormat:@"is%@",Key];
if ([ivarNameArray containsObject:_key]) {
Ivar ivar = class_getInstanceVariable([self class], _key.UTF8String);
object_setIvar(self, ivar, value);
return;
}
if ([ivarNameArray containsObject:_isKey]) {
Ivar ivar = class_getInstanceVariable([self class], _isKey.UTF8String);
object_setIvar(self, ivar, value);
return;
}
if ([ivarNameArray containsObject:isKey]) {
Ivar ivar = class_getInstanceVariable([self class], isKey.UTF8String);
object_setIvar(self, ivar, value);
return;
}
if (![[self class] accessInstanceVariablesDirectly]) {
@throw [NSException exceptionWithName:@"YXUnknowKey" reason:[NSString stringWithFormat:@"找不到%@的相关set方法",key] userInfo:nil];
}
}
- (BOOL)yx_performSelectorWithSelName:(NSString *)name value:(id)value
{
if ([self respondsToSelector:NSSelectorFromString(name)]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[self performSelector:NSSelectorFromString(name) withObject:value];
#pragma clang diagnostic pop
return YES;
}
return NO;
}
- (NSMutableArray *)getIvarsNameArray
{
NSMutableArray *nameArray = [[NSMutableArray alloc] init];
unsigned int count = 0;
Ivar *ivars = class_copyIvarList([self class], &count);
for (int i = 0; i < count; i ++) {
Ivar ivar = ivars[i];
const char * ivarName = ivar_getName(ivar);
NSString *ivarStr = [NSString stringWithUTF8String:ivarName];
[nameArray addObject:ivarStr];
}
return nameArray;
}
@end
- 上述只是简单的KVC实现,如果想了解更多,可以看看这个反汇编实现
网友评论