KVO/KVC系列

在iOS开发过程中,我们经常会听到或者用到KVO/KVC,但是对于什么是KVO和KVC,我们可能没有那么了解。下面先让我们来了解一下什么是KVC.
什么是KVO
在苹果的官方文档中是这样描述KVC的:它是一种通过字符串描述符而不是通过调用访问方法或者直接使用实例变量的非直接的访问对象属性的机制,说白了就是KVO是一种通过非常规方法访问成员变量或者属性的机制,这种非常规方式就是通过一个字符串标示符也就是所谓的key来访问属性或者成员变量。而这个key一般就是属性名或者实例变量名。
对于KVC的基本的方法都定义在NSKeyValueCoding的非正式协议中,并且NSObject默认实现了该协议。
KVC不仅支持对象类型,也支持数值类型和结构体。非对象类型的参数和返回类型会自动封装成NSValue或NSNumber类型。

KVO可以用来访问三种不同的对象值类型:属性、一对一关系、一对多关系
属性可以是诸如数值、字符串、bool类型等简单的值。也可以NSNumber或者NSColor这样的对象值。
在一对一关系里的对象可以拥有它自己的属性,这些属性可以在不改变对象的情况下被改变。像UIView的superView的属性,我们可以更改superView的属性,而不需要更改UIView。
一对多属性是一些相关对象的集合。通常用NSArray或者NSSet来存储这些集合。KVO也允许用户自定义集合类,但依然是像访问NSArray或者NSSet一样访问它们。

键和键路径
键是用来标识一个对象属性的字符串。一般情况下,键就是访问方法或者是对象的实例变量的名字。键必须是ASCII编码,以小写字母开头,并且不能包含空格。举几个键的例子:age、firstName、lastNmame等。
键路径是一串由点分隔的键组成的字符串,它是用来遍历一系列的对象属性的。第一个键的属性是跟接收者相关的,而每一个子系列是跟前一个属性相关的。比如键路径address.street,这个键路径会首先从接收者获得address属性,然后从address属性中获得street属性。
用KVC获得属性的值
方法valueForKey:会返回跟接收者相关的key的值。如果对于指定的key没有访问器或者实例变量,则给自己发送一个valueForUndefinedKey:消息,这个方法的默认实现是抛出一个NSUndefinedKeyException。子类可以重写这个方法。
同样的,valueForKeyPath:返回跟接收者相关的键路径的值。对于子系列中任何不遵循KVC的对象,都会收到一个valueForUndefineKey:消息。
dictionaryWithValuesForKeys:会检索数组中所有跟接收者相关的key的值。返回的NSDictionary中包含了数组中所有key的值。
注意:集合对象,比如NSArray、NSSet和NSDictionary中不能将nil作为一个值。相反的,应该用NSNull对象代替nil。NSNull是一个代表nil的对象属性。dictionaryWithValuesForKeys:和setValuesForKeysWithDictionary:方法的实现中,默认会在nil和NSNull之间进行转换。在你的对象中,不需要对nil做显示的测试。
用KVC设置属性的值
方法setValue:forKey:是将接收者中相关key的值设置成指定的值。在这个方法的实现中,会将NSValue的值转换成普通的数值然后赋给属性。
如果指定的key不存在,会给接收者发送一个setValue:forUndefinedKey:消息。这个方法的默认实现是抛出一个NSUndefinedKeyException异常,子类可以重写这个方法来自定义默认行为。
方法setValue:forKeyPath:的实现跟前面的一样,只不过它是用来处理键路径的。
setValuesForKeysWithDictionary:方法是用指定字典里的值来赋给接收者相关的属性。这个方法的默认实现是对每一对键-值都调用一次setValue:forKey:方法,并且自动将nil转成NSNull。
最后,你要关心的当尝试将一个nil值赋给一个非对象类型的时候该怎么办。这种情况下,接收者会发出一个setNilValueForKey:消息,这个方法的默认实现是抛出一个NSInvalidArgumentException。在你的应用中可以重写这个方法来定义一个默认值,然后用新的值触发setValue:forKey:。

在Cocoa中,NSObject默认实现了NSKeyValueCoding协议,也就是说,我们不需要自己再去实现NSKeyValueCoding协议,这极大的方便了我们的编程。试用了KVC之后,我们不仅可以通过访问方法来设置和修改属性的值,也可以通过NSKeyValueCoding提供的方法setVAlue:forKey和valueForKey:来设置和获得属性的值。

接下来,让我们先顶一个一个Person类:

[cpp] view plaincopy

- @interface Person : NSObject {
-     NSString *_firstName;
-     NSString *lastName;
-     BOOL married;
- }
-
- @property (nonatomic, strong) NSString *address;
- @property (nonatomic, assign) NSInteger age;
-
- @end

在Person类中,我们定义了三个成员变量和两个属性。一般情况下,我们只能通过成员变量名访问成员变量,并且只能是Person类内部使用,外部的类是无法访问Person的成员变量,对于属性,无论是在类内还是在类外都可以通过属性的set和get方法进行访问,下面我们通过KVC的方法来设置Person类属性和成员变量的值。

[cpp] view plaincopy

- Person *person = [[Person alloc] init];
- person.address = @"北京市朝阳区";
- [person setValue:[NSNumber numberWithBool:YES] forKey:@"married"];
- [person setValue:[NSNumber numberWithInt:26] forKey:@"age"];
- [person setValue:@"倾城" forKey:@"firstName"];
- [person setValue:@"吕" forKey:@"lastName"];

除了address属性之外,其他属性(或成员变量)的值都是通过KVC提供的方法来设置的。接下来,我们将属性(或成员变量)的值读出来看看是不是与我们设置的值是一样的。对于使用KVC方法设置的值,我们不通过KVC的方法来读取,而是使用普通的方法,对于普通方法设置的值,我们通过KVC提供的方法来读取.

[cpp] view plaincopy

- [person valueForKey:@"address"]);
- NSLog(@"Age:%d",person.age);
-
- NSLog(@"Married:%d",[person isMarried]);
- NSLog(@"First Name: %@", [person firstName]);
- NSLog(@"Last Name: %@", [person lastName]);

输出结果为:

[cpp] view plaincopy

- 2013-08-08 11:43:04.810 TestKVC[2978:c07] Address:北京市朝阳区
- 2013-08-08 11:43:04.812 TestKVC[2978:c07] Age:26
- 2013-08-08 11:43:04.812 TestKVC[2978:c07] Married:1
- 2013-08-08 11:43:04.813 TestKVC[2978:c07] First Name: 倾城
- 2013-08-08 11:43:04.813 TestKVC[2978:c07] Last Name: 吕

与我们之前的设置一样。

对于成员变量,我们不能使用属性的get方法来访问,必须自己实现成员变量的访问方法。在这里,我们也可以使用KVC提供的valueForKey:方法来访问成员变量,这样我们就不需要自己实现成员变量的set和get方法了。

在上面的例子中,我们既有属性,又有成员变量。成员变量的命名有带下划线的,有不带下划线的。那么KVC是如何来查找这些东西的呢。

setValue:forKey:的搜索模式

  • 如果没有并且接收者类的accessInstanceVariableDirectly的方法返回YES,接下来就要搜索成员变量了,顺序是:_<key>,_is<key>,<key>,is<key>
  • 如果都没有找到,接收者类的setValue:forUndefinedKey:方法将被调用

valueForKey:的搜索模式

  • 按get<Key>,<key>,is<Key>的顺序搜索名字相符的访问方法,找到之后,相应的方法就会被调用。如果这个方法的返回类型是一个对象,那么结果将会被直接返回。如果返回的是NSNumber支持的数值类型,则将结果封装成NSNumber进行返回。如果不是NSNumber支持的类型,则会将结果转成NSValue进行返回(NSValue支持的类型包括NSPoint、NSRange、NSrect和NSSize等)
  • 如果上面的方法都没有找到,接下来会搜索接收者类中的countOf<Key>和objectIn<Key>AtIndex和<key>AtIndexes。如果countOf<Key>方法和后面两个之一被找到的话,一个能响应所有NSArray方法的集合代理对象将会被返回。每一个发送给结合代理对象的NSArray消息都会使发送给接收者的valueForKey:的消息和countOf<Key>、objectIN<Key>AtIndex和<key>AtIndexes之间产生某种关联。如果接收者类也实现了名字如get<Key>:range:的可选方法,为了提高性能,这个方法将会在合适的时候被调用。
  • 如果简单访问方法和NSArray的方法都没有被找到的话,接下来就会搜索接收者类中名字如下的一组方法:countOf<Key>,enumeratorOf<Key>和memberOf<Key>。如果这三个方法都被找到的话,一个能够响应所有NSSet方法的集合代理对象会被返回。每一个发给集合代理对象的NSSet消息都会使接收者类的valuefForyKey:的消息和countOf<Key>,enumeratorOf<Key>和memberOf<Key>之间产生某种关联。
  • 如果上面的搜索都没有结果并且接收者类的accessInstanceVariableDirectly方法返回YES,接收者类按_<key>,_is<Key>,<key>,is<Key>的顺序搜索成员变量,如果找到了,成员变量的值将会被返回。如果成员变量的值类型是NSNumber支持的类型,将值进行转换之后返回一个NSNumber对象。否则将转换成NSValue对象进行返回。
  • 如果还是什么都没有找到的话,就会调用valueForUndefinedKey:

如果你不想要setValue:forUndefiedKey:和valueForUndefinedKey的默认实现,你就需要自己重写这两个方法。

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

推荐阅读更多精彩内容

  • KVC(Key-value coding)键值编码,单看这个名字可能不太好理解。其实翻译一下就很简单了,就是指iO...
    黑暗中的孤影阅读 49,743评论 74 441
  • KVC(Key-value coding)键值编码,单看这个名字可能不太好理解。其实翻译一下就很简单了,就是指iO...
    Fendouzhe阅读 675评论 0 6
  • KVC简单介绍 KVC(Key-value coding)键值编码,就是指iOS的开发中,可以允许开发者通过Key...
    公子无礼阅读 1,396评论 0 6
  • KVC(Key-value coding)键值编码,iOS的开发中,可以允许开发者通过Key名直接访问对象的属性,...
    CALayer_Sai阅读 2,525评论 0 4
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,673评论 18 139