一、定义
KVC(Key-value coding)键值编码,对NSObjcet的扩展,开发者通过Key名直接访问对象的属性,或者给对象的属性赋值。而不需要调用明确的存取方法。
二、主要使用场景
1.动态地设值和取值
可以在运行时动态地访问和修改对象的属性;例如进行json转model操作时,可以使用runtime来获取成员变量,并利用KVC进行修改。
2.访问和修改私有变量
当我们需要访问和修改.m文件中声明私有属性,KVC是一大利器;例如更改某些三方库或者系统的私有变量。
三、调用顺序
对于平时开发来说,调用顺序我们并不需要太多了解,为了更好的理解实现原理,这里进行简单梳理
新建Person
@interface Person : NSObject
{
NSString *_name;
NSString *_isName;
NSString *name;
NSString *isName;
}
@end
@implementation Person
-(instancetype)init {
if (self = [super init]) {
_name = @"这是_name";
_isName = @"这是_isName";
name = @"这是name";
isName = @"这是isName";
}
return self;
}
@end
- (void)viewDidLoad {
[super viewDidLoad];
Person *person = [[Person alloc] init];
NSString *name = [person valueForKey:@"name"];
NSLog(@"%@",name);
}
打印结果
2020-04-13 17:12:27.622399+0800 Algorithm[18089:316164] 这是_name
1
这个时候假设没有"_name"属性
@interface Person : NSObject
{
// NSString *_name;
NSString *_isName;
NSString *name;
NSString *isName;
}
@end
@implementation Person
-(instancetype)init {
if (self = [super init]) {
// _name = @"这是_name";
_isName = @"这是_isName";
name = @"这是name";
isName = @"这是isName";
}
return self;
}
@end
打印结果
2020-04-13 17:19:57.662839+0800 Algorithm[18327:321939] 这是_isName
由此依次操作,可查找一个命名规则为_name、_isName、name、isName的实例变量。根据这个顺序,如果发现则获取实例变量值。
你以为这样就结束了,不可能的,这个时候来一个骚操作
// Person.m
// 是否允许读取实例变量的值,如果为YES则在KVC查找的过程中,从内存中读取属性、实例变量的值。
// 如果不允许外界通过KVC对我们的私有属性和成员变量进行操作,则可以设置此值为NO。
+ (BOOL)accessInstanceVariablesDirectly {
return NO;
}
这个时候就崩溃了,可以看出不能进行访问属性
2020-04-14 13:35:21.245193+0800 Algorithm[10324:177961] *** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<Person 0x600003fc9320> valueForUndefinedKey:]: this class is not key value coding-compliant for the key name.'
再来一个骚操作
// Person.m
-(NSString *)getName {
return @"这是方法getName";
}
-(NSString *)name {
return @"这是方法name";
}
-(NSString *)isName {
return @"这是方法isName";
}
加入上面方法后又正常了,完整的调用顺序如下:
1、查找是否实现getter方法,依次匹配-get<Key> 和 -<key> 和 -is<Key>和-_<key>,如果找到,直接返回。
2、当没有找到getter方法,调用accessInstanceVariablesDirectly询问
如果返回YES, _<key> ,_is<Key>,<key>,is<Key>,找到了返回对应的值
如果返回NO,结束查找。并调用 valueForUndefinedKey: 报异常
3、如果没找到getter方法和属性值,调用 valueForUndefinedKey: 报异常
四、实现原理
结合demo,了解实现原理(感觉苹果的底层原理大多都是和runtime有关。。。这个也不例外)直接上代码
// NSObject+MyKVC.h
@interface NSObject (MyKVC)
-(void)my_setValue:(id)value forKey:(NSString*)key;
-(id)my_valueforKey:(NSString*)key;
@end
// NSObject+MyKVC.m
#import "NSObject+MyKVC.h"
#import <objc/runtime.h>
@implementation NSObject (MyKVC)
-(void)my_setValue:(id)value forKey:(NSString *)key {
if (key == nil || key.length == 0) {
return;
}
if (value == nil) {
[self setNilValueForKey:key];
return;
}
if (![value isKindOfClass:[NSObject class]]) {
@throw @"必须是NSObject类型";
return;
}
NSString *setter = [[@"set" stringByAppendingString:[key capitalizedString]] stringByAppendingString:@":"];
if ([self respondsToSelector:NSSelectorFromString(setter)]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[self performSelector:NSSelectorFromString(setter) withObject:value];
#pragma clang diagnostic pop
}else{
// 获取属性列表
unsigned int count;
BOOL flag = false;
Ivar* vars = class_copyIvarList([self class], &count);
for (NSInteger i = 0; i<count; i++) {
Ivar var = vars[I];
NSString* keyName = [NSString stringWithCString:ivar_getName(var) encoding:NSUTF8StringEncoding];
// 按顺序匹配到就结束
if ([keyName isEqualToString:[NSString stringWithFormat:@"_%@",key]]) {
flag = true;
object_setIvar(self, var, value);
break;
}
if ([keyName isEqualToString:[NSString stringWithFormat:@"_is%@",key.capitalizedString]]) {
flag = true;
object_setIvar(self, var, value);
break;
}
if ([keyName isEqualToString:key]) {
flag = true;
object_setIvar(self, var, value);
break;
}
if ([keyName isEqualToString:[NSString stringWithFormat:@"is%@",key.capitalizedString]]) {
flag = true;
object_setIvar(self, var, value);
break;
}
}
// 没有找到
if (!flag) {
[self setValue:value forUndefinedKey:key];
}
}
}
-(id)my_valueforKey:(NSString *)key{
if (key == nil || key.length == 0) {
return nil;
}
//查找getter方法
NSString* getFuncName = [NSString stringWithFormat:@"get%@",key.capitalizedString];
NSString* isFuncName = [NSString stringWithFormat:@"is%@",key.capitalizedString];
// 查找 -get<Key>
if ([self respondsToSelector:NSSelectorFromString(getFuncName)]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
return [self performSelector:NSSelectorFromString(getFuncName)];
#pragma clang diagnostic pop
} else if ([self respondsToSelector:NSSelectorFromString(key)]) { // 查找 -<key>
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
return [self performSelector:NSSelectorFromString(key)];
#pragma clang diagnostic pop
} else if ([self respondsToSelector:NSSelectorFromString(isFuncName)]) { // 查找 is<Key>
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
return [self performSelector:NSSelectorFromString(isFuncName)];
#pragma clang diagnostic pop
} else { // 询问 accessInstanceVariablesDirectly,是否继续查找属性,默认返回YES
// 先看顺序:_<key>, _is<Key>, <key>, is<Key>
// 获取属性列表
unsigned int count;
BOOL flag = false;
Ivar* vars = class_copyIvarList([self class], &count);
for (NSInteger i = 0; i<count; i++) {
Ivar var = vars[I];
NSString* keyName = [NSString stringWithCString:ivar_getName(var) encoding:NSUTF8StringEncoding];
// 按顺序匹配到就结束
if ([keyName isEqualToString:[NSString stringWithFormat:@"_%@",key]]) {
flag = true;
return object_getIvar(self, var);
break;
}
if ([keyName isEqualToString:[NSString stringWithFormat:@"_is%@",key.capitalizedString]]) {
flag = true;
return object_getIvar(self, var);
break;
}
if ([keyName isEqualToString:key]) {
flag = true;
return object_getIvar(self, var);
break;
}
if ([keyName isEqualToString:[NSString stringWithFormat:@"is%@",key.capitalizedString]]) {
flag = true;
return object_getIvar(self, var);
break;
}
}
// 没有找到
if (!flag) {
[self valueForUndefinedKey:key];
}
return nil;
}
return nil;
}
@end
调用代码
- (void)viewDidLoad {
[super viewDidLoad];
Person *person = [[Person alloc] init];
NSString *name = [person my_valueforKey:@"name"];
NSLog(@"%@",name);
}
原创出处:https://blog.csdn.net/qq_32644987/article/details/105489083