kvc

一、定义

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);
}

image.png
image.png

原创出处:https://blog.csdn.net/qq_32644987/article/details/105489083

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容

  • KVC定义 KVC(Key-value coding)键值编码,就是指iOS的开发中,可以允许开发者通过Key名直...
    SheIsMySin_72e7阅读 376评论 0 0
  • KVC KVC定义 KVC(Key-value coding)键值编码,就是指iOS的开发中,可以允许开发者通过K...
    暮年古稀ZC阅读 2,127评论 2 9
  • KVC KVC定义 KVC(Key-value coding)键值编码,就是指iOS的开发中,可以允许开发者通过K...
    恋空K阅读 706评论 0 2
  • image.png KVC KVC定义 KVC(Key-value coding)键值编码,就是指iOS的开发中,...
    草根小强阅读 276评论 0 0
  • 1. Basic methods KVC(Key-value coding)键值编码,就是指iOS的开发中,可以允...
    木小易Ying阅读 188评论 0 4