KVC实现原理

博客链接KVC实现原理

KVC全称是Key Value Coding,定义在NSKeyValueCoding.h文件中。KVC提供了一种间接访问其属性方法或成员变量的机制,可以通过字符串来访问对应的属性方法或成员变量。关于KVC的实现主要依赖于其搜索规则。

搜索规则

在赋值过程中,我们会使用- (void)setValue:(id)value forKey:(NSString *)key或者(void)setValue:(id)value forKeyPath:(NSString *)keyPath;进行KVC的赋值操作。在取值过程中,我们会使用- (id)valueForKey:(NSString *)key;或者- (id)valueForKeyPath:(NSString *)keyPath;

KVC在通过key或者keyPath进行操作的时候,可以查找属性方法、成员变量等,查找的时候可以兼容多种命名。具体的查找规则在KVC官方文档中可以找到。

KVC的实现主要依赖于settergetter方法,所以关于命名需要符合苹果的规范。另外在搜索过程中accessInstanceVariablesDirectly这个只读属性也起着重要的作用。这个属性表示是否允许读取实例变量的值,如果为YES则在KVC查找的过程中,从内存中读取属性实例变量的值,默认为YES。

赋值原理

setValue:forKey:为例,其内部实现主要有以下步骤:

  1. set<Key>:_set<Key>的顺序查找对应命名的setter方法,如果找到的话,调用这个方法并将值传进去(根据需要进行对象转换);

  2. 如果没有发现setter方法,但是accessInstanceVariablesDirectly类属性返回YES,则按_<key>_is<Key><key>is<Key>的顺序查找一个对应的实例变量。如果发现则将value赋值给实例变量;

  3. 如果没有发现setter方法或实例变量,则调用setValue:forUndefinedKey:方法,默认抛出一个异常,但是一个NSObject的子类可以提出合适的行为。

接着我们用代码进行相关的测试:

实验1:验证setter方法

// model1
@interface KVCTestModel1 : NSObject

@end

@implementation KVCTestModel1

- (void)setName:(NSString *)name {
    NSLog(@"%s", __func__);
}

- (void)_setName:(NSString *)name {
    NSLog(@"%s", __func__);
}

@end

// model2
@interface KVCTestModel2 : NSObject

@end

@implementation KVCTestModel2

- (void)_setName:(NSString *)name {
    NSLog(@"%s", __func__);
}

@end

// model3
@interface KVCTestModel3 : NSObject

@end

@implementation KVCTestModel3

@end

// 调用
- (void)_testKVC {
    KVCTestModel1 *model1 = [[KVCTestModel1 alloc] init];
    [model1 setValue:@"Nero" forKey:@"name"];
    
    KVCTestModel2 *model2 = [[KVCTestModel2 alloc] init];
    [model2 setValue:@"Nero" forKey:@"name"];
    
    KVCTestModel3 *model3 = [[KVCTestModel3 alloc] init];
    [model3 setValue:@"Nero" forKey:@"name"];
}

执行结果如下:


kvc_setter1

实验2:验证实例变量

// model1
@interface KVCTestModel1 : NSObject {
    NSString *_name;
    NSString *_isName;
    NSString *name;
    NSString *isName;
}

@end

// model2
@interface KVCTestModel2 : NSObject {
    NSString *_isName;
    NSString *name;
    NSString *isName;
}

@end

// model3
@interface KVCTestModel3 : NSObject {
    NSString *name;
    NSString *isName;
}

@end

// model4
@interface KVCTestModel4 : NSObject {
    NSString *isName;
}

@end

// 调用
- (void)_testKVC {
    self.kvcTestModel1 = [[KVCTestModel1 alloc] init];
    [self.kvcTestModel1 setValue:@"Nero" forKey:@"name"];
    
    self.kvcTestModel2 = [[KVCTestModel2 alloc] init];
    [self.kvcTestModel2 setValue:@"Nero" forKey:@"name"];

    self.kvcTestModel3 = [[KVCTestModel3 alloc] init];
    [self.kvcTestModel3 setValue:@"Nero" forKey:@"name"];

    self.kvcTestModel4 = [[KVCTestModel4 alloc] init];
    [self.kvcTestModel4 setValue:@"Nero" forKey:@"name"];
}

执行结果如下:


kvc_setter2

另外如果设置accessInstanceVariablesDirectly返回为NO,即使有符合命名规范的实例变量名,KVC也无法赋值成功;setValue:forUndefinedKey:默认会抛出一个异常,你可以用重写这个方法用来拦截。

赋值原理流程图如下:


kvc_setter_process.jpg

取值原理

valueForKey:为例,其内部实现主要有以下几步:

  1. 通过getter方法搜索实例,以get<Key>, <key>, is<Key>, _<key>的顺序搜索符合规则的方法,如果有,就调用对应的方法;

  2. 如果没有发现简单getter方法,并且在类方法accessInstanceVariablesDirectly是返回YES的的情况下搜索一个名为_<key>_is<Key><key>is<Key>的实例;

  3. 如果返回值是一个对象指针,则直接返回这个结果;如果返回值是一个基础数据类型,但是这个基础数据类型是被NSNumber支持的,则存储为NSNumber并返回;如果返回值是一个不支持NSNumber的基础数据类型,则通过NSValue进行存储并返回;

  4. 在上述情况都失败的情况下调用valueForUndefinedKey:方法,默认抛出异常,但是子类可以重写此方法。

由于和前面的赋值原理实验相似,这里就不添加相关的验证代码了。另外valueForKey:返回的结果还可能是数组或者其他集合类型,所以在上面第1步和第2步之间还有一些其他的规则,这些规则用来判断是否是数组或者其他集合类型的规则,但是我觉得忽略这些规则跟整体流程理解冲突不大,所以就忽略掉了(具体的在官在KVC官方文档中可以找到。)。

补充:

面试题分析: KVC能否能够触发KVO

答案是肯定的。

测试代码如下:

@interface KVCTestModel1 : NSObject {
    @public
    NSString *_name;
}

@end

@implementation KVCTestModel1

@end

// 测试代码
KVCTestModel1 *model1 = [[KVCTestModel1 alloc] init];
[model1 addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];

// 直接修改成员变量
model1 -> _name = @"Nero1";
NSLog(@"%@", [model1 valueForKey:@"name"]);

// 手动触发KVO
[model1 willChangeValueForKey:@"name"];
model1 -> _name = @"Nero2";
[model1 didChangeValueForKey:@"name"];
NSLog(@"%@", [model1 valueForKey:@"name"]);

// KVC赋值
[model1 setValue:@"Nero3" forKeyPath:@"name"];
NSLog(@"%@", [model1 valueForKey:@"name"]);

[model1 removeObserver:self forKeyPath:@"name"];

打印结果:


image

通过上面的代码我们可以认为,在以KVC的方式对变量进行赋值的时候,会判断该对象是否使用了KVO,如果是,则会触发KVO。

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

推荐阅读更多精彩内容

  • KVC(Key-value coding)键值编码,单看这个名字可能不太好理解。其实翻译一下就很简单了,就是指iO...
    我的梦工厂阅读 889评论 1 8
  • KVC(Key-valuecoding)键值编码,单看这个名字可能不太好理解。其实翻译一下就很简单了,就是指iOS...
    榕樹頭阅读 695评论 0 2
  • 一、什么是KVC? KVC是Key Value Coding键值编码,是一种通过字符串的名字(Key)来访问类属性...
    Coder_LRT阅读 386评论 0 0
  • 一、什么是KVC? KVC是Key Value Coding键值编码,是一种通过字符串的名字(Key)来访问类属性...
    DeveloperBlock阅读 463评论 0 0
  • KVC简单介绍 KVC(Key-value coding)键值编码,就是指iOS的开发中,可以允许开发者通过Key...
    公子无礼阅读 1,379评论 0 6