iOS进阶之详解KVC流程

简介

KVC(Key-value coding)键值编码。就是指iOS的开发中,可以允许开发者通过Key名直接访问对象的属性,或者给对象的属性赋值。而不需要调用明确的存取方法。这样就可以在运行时动态地访问和修改对象的属性。而不是在编译时确定,这也是iOS开发中的黑魔法之一。

KVC在iOS的定义

  • KVC的定义是对NSObject的扩展来实现的(Objective-C中有个显式的NSKeyValueCoding类别名)。
  • 几乎所有的Objective-C对象都能使用KVC

下面介绍一下KVC四个最为重要的方法

//通过key来取值  
- (nullable id)valueForKey:(NSString *)key; 

//通过key来设值
- (void)setValue:(nullable id)value forKey:(NSString *)key;

//通过KeyPath来取值
- (nullable id)valueForKeyPath:(NSString *)keyPath;  

//通过KeyPath来设值
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath; 

当然 NSKeyValueCoding类别中还有其他的一些方法,下面列举一些

//默认返回YES,表示如果没有找到Set方法的话,
会按照_key,_iskey,key,iskey的顺序搜索成员,
设置成NO就不这样搜索
+ (BOOL)accessInstanceVariablesDirectly;

//KVC提供属性值正确性验证的API,它可以用来检查set的值是否正确、
为不正确的值做一个替换值或者拒绝设置新值并返回错误原因。
- (BOOL)validateValue:(inout id __nullable * __nonnull)ioValue 
forKey:(NSString *)inKey error:(out NSError **)outError;    

//如果Key不存在,且没有KVC无法搜索到任何和Key有关的字段
或者属性,则会调用这个方法,默认是抛出异常。
- (nullable id)valueForUndefinedKey:(NSString *)key;   

//和上一个方法一样,但这个方法是设值。
- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;
  

//如果你在SetValue方法时面给Value传nil,则会调用这个方法
- (void)setNilValueForKey:(NSString *)key;

//输入一组key,返回该组key对应的Value,再转成字典返回,用于将Model转到字典。
- (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;

KVC底层执行流程

当调用setValue:@"Jack" forKey:@"name"的代码时,底层的执行机制如下:

  1. 程序优先遍历类对象的方法列表去找调用setName:方法,找到setter方法
  2. 找到setter方法之后才会遍历类对象的成员列表找到_name赋值。
  3. 如果没有找到setName:方法,那就继续找_setName方法,
  4. 如果还没有没有的KVC机制会检查+ (BOOL)accessInstanceVariablesDirectly方法有没有返回YES,默认该方法会返回YES. 返回NO就是不允许遍历类的成员变量
  5. 如果你重写了该方法让其返回NO的话,,那么这一步KVC会执行setValue:forUndefinedKey:方法,不过一般开发者不会这么做。
  6. KVC机制会搜索该类成员变量列表里面有没有名为的成员变量,无论该变量是在类接口处定义,还是在类实现处定义,也无论用了什么样的访问修饰符,只在存在以命名的变量,KVC都可以对该成员变量赋值。
  7. 类即没有set:方法,也没有_成员变量,KVC机制会搜索_is的成员变量。
  8. 该类即没有set:方法,也没有_和_is成员变量,KVC机制再会继续搜索和is的成员变量。再给它们赋值。
  9. 上面列出的方法或者成员变量都不存在,系统将会执行该对象的setValue:forUndefinedKey:方法,默认是抛出异常。

下面我们用代码验证一下:

//Person.h
@interface Person : NSObject  {
//不声明@public,Person无法用->来访问成员变量
@public
    NSString *_name;
    NSString *_isName;
    NSString *name;
    NSString *isName;
}
//系统会自动生成setter getter方法
@property (nonatomic, copy) NSString *name;

@end
//person.m文件
@implementation Person

//当找不到set方法,会调用用这个方法
+ (BOOL)accessInstanceVariablesDirectly {

    NSLog(@"accessInstanceVariablesDirectly");
    return NO;
}
@end

然后在调用该对象并且setValue: forKey:赋值

Person *p = [[Person alloc] init];
[p setValue:@"张三" forKey:@"name"];

NSLog(@"getName: %@", p.name);
NSLog(@"valueforkey: %@",[p valueForKey:@"name"]);
NSLog(@"_name: %@", p->_name);
NSLog(@"_isName: %@", p->_isName);
NSLog(@"name: %@", p->name);
NSLog(@"isName: %@", p->isName);

执行结果:

getName: 张三
valueforkey: 张三
_name: 张三
_isName1: (null)
name: (null)
isName: (null)

说明setValue: forKey:会对该对象的类对象的方法列表进行遍历,如果找到setName方法成功之后,才会遍历类对象的成员变量找到_name进行赋值。

当注释掉@property (nonatomic, copy) NSString *name;,不让Person自动生成对象setter getter方法,看看怎么执行的

//Person.h
@interface Person : NSObject  {
//不声明@public,Person无法用->来访问成员变量
@public
    NSString *_name;
    NSString *_isName;
    NSString *name;
    NSString *isName;
}
//系统会自动生成setter getter方法
//@property (nonatomic, copy) NSString *name;

@end
//person.m文件
@implementation Person

//当找不到set方法,会调用用这个方法
+ (BOOL)accessInstanceVariablesDirectly {

    NSLog(@"accessInstanceVariablesDirectly");
    return NO;
}

-(id)valueForUndefinedKey:(NSString *)key{
    
    NSLog(@"获取值出现异常,该key不存在%@",key);
    return nil;
}

-(void)setValue:(id)value forUndefinedKey:(NSString *)key{
    
    NSLog(@"赋值出现异常,该key不存在%@",key);
}
@end

然后在调用该对象并且setValue: forKey:赋值

Person *p = [[Person alloc] init];
[p setValue:@"张三" forKey:@"name"];

//NSLog(@"getName: %@", p.name);//没有getter方法了
NSLog(@"valueforkey: %@",[p valueForKey:@"name"]);
NSLog(@"_name: %@", p->_name);
NSLog(@"_isName: %@", p->_isName);
NSLog(@"name: %@", p->name);
NSLog(@"isName: %@", p->isName);

执行结果:

// 遍历set、get方法没找到,执行两次
accessInstanceVariablesDirectly
accessInstanceVariablesDirectly
赋值出现异常,该key不存在name
accessInstanceVariablesDirectly
accessInstanceVariablesDirectly
获取值出现异常,该key不存在name
valueforkey: (null)
_name: (null)
_isName: (null)
name: (null)
isName: (null)

说明了accessInstanceVariablesDirectly返回NO的话,不在遍历类对象成员变量赋值。就会调用setValue: forUndefinedKey。没有setter方法,就没再类对象成员列表去找_name.

那我们把accessInstanceVariablesDirectly返回的事YES,执行结果:

accessInstanceVariablesDirectly
accessInstanceVariablesDirectly
valueforkey: 张三
_name: 张三
_isName: (null)
name: (null)
isName: (null)

说明先找遍历_name变量,找到之后就退出遍历。不在遍历其他变量。
那就把对象的_name去掉

NSLog(@"valueforkey: %@",[p valueForKey:@"name"]);
NSLog(@"_isName: %@", p->_isName);
NSLog(@"name: %@", p->name);
NSLog(@"isName: %@", p->isName);

执行结果:

accessInstanceVariablesDirectly
accessInstanceVariablesDirectly
valueforkey: 张三
_isName: 张三
name: (null)
isName: (null)

说明在类的成员列表里遍历没见到_name变量,在遍历会找_isName,找到之后就退出遍历。不在遍历其他变量。那我们把_isName也去掉。打印一下:

NSLog(@"valueforkey: %@",[p valueForKey:@"name"]);
NSLog(@"name: %@", p->name);
NSLog(@"isName: %@", p->isName);

执行结果:

accessInstanceVariablesDirectly
accessInstanceVariablesDirectly
valueforkey: 张三
name: 张三
isName: (null)

说明在类的成员列表里遍历没见到_name、_isName变量,在遍历会找name变量,找到之后就退出遍历。不在遍历其他变量。那我们把name也去掉。打印一下:

NSLog(@"valueforkey: %@",[p valueForKey:@"name"]);
NSLog(@"isName: %@", p->isName);

执行结果打印一下:

accessInstanceVariablesDirectly
accessInstanceVariablesDirectly
valueforkey: 张三
isName: 张三

说明在类的成员列表里遍历没见到_name、_isName、name变量,在遍历找到isName变量,找到之后就退出遍历,不在遍历其他变量。把变量都注释掉。打印一下:

NSLog(@"valueforkey: %@",[p valueForKey:@"name"]);

// 遍历set、get方法没找到,执行两次
accessInstanceVariablesDirectly
accessInstanceVariablesDirectly
赋值出现异常,该key不存在name
accessInstanceVariablesDirectly
accessInstanceVariablesDirectly
获取值出现异常,该key不存在name
valueforkey: (null)

当执行setvalue:forkey没有set方法、并且依次遍历_name、_isName、name、isName没有的时候就会执行setValue:(id)value forUndefinedKey并默认是报错的。重写方法做处理不会报错。

KVC的使用

  • 动态地取值和设值

    • 利用KVC动态的取值和设值是最基本的用途了。相信每一个iOS开发者都能熟练掌握,
  • 用KVC来访问和修改私有变量

    • 对于类里的私有属性,Objective-C是无法直接访问的,但是KVC是可以的。
  • Model和字典转换

    • 充分地运用了KVC和Objc的runtime组合的技巧,只用了短短数行代码就是完成了很多功能
  • KVO是基于KVC实现的

总结

通过分析KVC整个执行过程,了解NSKeyValueCoding这个分类的方法,是自己对KVC使用加深印象。

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

推荐阅读更多精彩内容

  • KVC(Key-valuecoding)键值编码,单看这个名字可能不太好理解。其实翻译一下就很简单了,就是指iOS...
    榕樹頭阅读 667评论 0 2
  • 1. Basic methods KVC(Key-value coding)键值编码,就是指iOS的开发中,可以允...
    木小易Ying阅读 177评论 0 4
  • 前言:往往会某项工具WORK,就想究其原理。本文先简单介绍KVC 一、KVC 简介 1.1 KVC 概述 1.KV...
    梦蕊dream阅读 875评论 0 2
  • 此时此刻好困好累 今天上午看了外科的泌尿 晕晕的 后来小王带来噩耗 啊 期末不要太虐才好 然后内科完毕 口腔的题没...
    Emily_e135阅读 96评论 0 0
  • 放暑假了,爸爸把我们接到了郑州,我心里美滋滋的,我们一家人终于能在一起了。 没过几天,弟弟就咳嗽了,妈妈带着弟弟去...
    幸福一家a阅读 228评论 0 1