第二十章 键值编码

什么是键值编码

什么是键值编码

访问实例变量可以通过访问器访问,也可以将属性设置为@public。

与此相对,键值编码(key-value coding)是指,将表示对象包含的信息的字符串作为键值使用,来间接访问该信息的方式。键值编码提供了非常强大的功能,基本上,只要存在访问器方法,声明属性或实例变量,就可以将其名字指定为字符串来访问。本章中,可以访问,设定的对象状态的值称为属性(property)。

之所以说键值编码的访问是间接的,是因为以下两点:

也可以在运行中确定作为键的字符串。

使用者无法知道实际访问属性的方法。

键值编码的基本处理

键值编码的必须方法在非正式协议NSKeyValueCoding中声明,这些默认在NSObject中实现。

有访问器的属性会使用该访问器,没有访问器的属性也可以设定值和访问。而且变量obj也可以为id类型。

访问属性

键值编码的方法的行为

下面的讲述围绕name进行。以下划线开始的名字不能用于方法名或实例变量名。此外,也不要使用以get开始的名字。

接收器中如果有name访问器则使用它。

没有访问器时,使用接收器的类方法accessInstanceVariablesDirectly来查询。返回YES时,如果存在实例变量name时(或_name,isName,_isName等)则返回其值。使用引用计数的方式时,实例变量如果是对象,则旧值会被自动释放,新值被保存并带入。

既没有访问器也没有实例变量时,将引起接收器调用方法setValue:forUndefinedKey:

应该返回的值如果不是对象,则返回被适当的对象包装的值。

但是,如果接收器包含与带索引的访问器模式一致的方法,则将返回有数组对象行为代理(proxy)对象。

accessInstanceVariablesDirectly只要该方法返回YES,实例变量的可见属性即使有@private修饰,也可以访问。

setValue:forKey:如果设定值失败,则调用下面的方法。

- (void)setValue:(id)value forUndefinedKey:(nonnullNSString *)key

//不能设置键字符串key对应的属性值时,从方法setValue:forKey中调用该方法。默认情况下,该方法的执行会触发异常NSUndefinedKeyException。不过,通过在子类中修改定义,就可以返回其他对象。

键值编码比访问器以及实例变量名更具灵活性。

由于键值编码所接收的对象都是id类型,因此,在该部分中,编译时不会进行仔细的类型检查。所以一定要注意不要传入与属性不符的对象。

使用键值编码的程序如何执行,不是由静态解析源码语法的结果决定的,而是使用程序运行时包含的信息动态决定的。与实例变量的可视属性不同,有方法决定是否可以访问实例变量这一点,会使人感觉有损一致性。总之,键值编码这一强大的功能就像一把双刃剑,也伴随着危险,因此不可以滥用。

属性值的自动转换

将属性中单纯的数值,也就是整数或实数,布尔值这样的数据称为标量(scalar)值。将标量值,结构体,字符串或NSNumber等常数对象称为属性(attribute)。

方法setValue:在返回值为标量值或结构体时,会返回将其自动包装的对象。另一方面,为了给setValue:forKey:传入值,也需要使用适当的对象来包装。

单纯的数值用NSNumber来包装,结构体用NSValue类的实例来包装。属性值如果为对象,则可以将nil作为值传递。另一方面,当将nil作为值传递时,setNilValueForKey:方法将被发送给接收器。

- (void)setNilValueForKey:(NSString *)key

//执行该方法将产生NSInvalidArgumentException异常

字典对象和键值编码

字典类NSDictionary和NSMutableDictionary包含了协议NSKeyValueCoding的方法,使用它们可以进行键值编码。

- (id)valueForKey:(NSString *)key

//键字符串开头不是@时,将调用方法objectForKey:。如果开头为”@“,则将去除开头字符后剩余的字符串作为键,调用超类的方法valueForKey:。

NSMutableDictionary中定义了以下的方法:

- (void)setValue:(id)value forKey:(NSString *)key

//一般会调用方法setObject:forKey:,参数value为nil时,调用方法removeObjectForKey:删除键对应的对象。

根据键路径进行访问

属性为对象时,该对象还可能持有属性。在键值编码中,使用某个键访问获得某个属性对象后,如果希望再用别的键来访问该对象,可采用如下方法:

idname = [aGroup valueForKey:@“leader.name"];

像这样,用“.”连接键表示的字符串称为键路径(key path)。只要能找到对象,点和键多长都没有关系。

声明属性的点是运算符,而这里的键路径则是一个字符串。

使用键路径访问属性的方法如下:(略)

一对一关系和一对多关系

使用键(或键路径)访问时,我们将对象确定为一个的属性称为指定一对一关系(to-one relationship)的属性,将属性值为数组或集合的属性称为指定一对多关系(to-many relationship)的属性。

如果键对应一个对象,那么也是一对一关系。

关于一对多关系属性的访问,更改,需要留意以下几点:

1.使用集合元素对象持有的键访问一对多关系属性时,键对应的属性被作为数组或集合返回。

2.使用集合元素对象持有的键设定一对多关系属性时,各元素对象键对应的属性全都被更改。

数组对象和键值编码

数组类NSArray和NSMutableArray以及集合类NSSet和NSMutableSet都包含协议NSKeyValueCoding的方法,也都有键值编码。

- (id)valueForKey:(NSString *)key//以key为参数,对集合的各元素调用方法valueForKey:后返回数组(NSSet时返回集合)。对各成员适用方法valueForKey:,返回nil时,则包含NSNull实例。

- (void)setValue:(id)value forKey:(NSString *)key//对集合各元素调用方法setValue:forKey:。需要注意的是,即使集合对象自身不可以改变,也能调用该方法。

一对多关系的访问

带索引的访问器模式

即使是非数组对象,如果有某个模式的访问器,也可以进行像数组一样的键值编码操纵。该访问器模式称为带索引的访问器模式(indexed accessor pattern)。

下面是两个方法等实现。下划线部分会输入字符串。

- (NSUInteger)countOf___;

- (id)objectIn___AtIndex:(NSUInteger)index;

为了提高运行效率,除上述两个方法外,还可以实现下面的方法。这也是和数组类簇中的getObjects:range:相同的方法。

- (void)get___:(id__unsafe_unretained[])aBuffer range:(NSRange)aRange;

一对多关系的可变访问

获得可变数组对象的方法。

- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key

//返回相当于用键字符串指定的一对多关系的属性的可变数组。操作被返回的数组与操作属性同时进行。

- (NSMutableArray *)NSMutableArrayValueForKeyPath:(NSString *)keyPath

//接收器属性在键路径中指定。

用该方法操作属性时,除了之前提到的访问常数数组需要添加的两个方法之外,还需要实现用来插入和删除的方法。下划线部分加入了键字符串(通常为复数形式)。使用这些方法并通过键值编码访问时,内部的代理就会作为数组进行操作了。

- (void)insertObject:(id)obj in___AtIndex:(NSUInteger)index;

- (void)removeObjectFrom___AtIndex:(NSUInteger)index;

不仅是上述两个方法,通过实现下面的方法也可以实现属性的可变访问。

- (void)set___:(id)anArray;

在实现该方法时,通过使用被作为参数传入的数组元素对象,就可以置换一对多关系的全部属性内容。

在添加了插入和删除的方法的基础上,如果能实现下面的方法,则将能显著改善运行效果。

- (void)replaceObjectIn___AtIndex:(NSUInteger)index withObject:(id)obj;

如果上述方法都没有实现,那么当存在与键字符串同名的实例变量且该变量又是可变数组的对象时,方法mutableArrayValueForKey:将直接返回该值。

KVC标准

验证属性值

在某些情况下,如果预期之外的对象被设定了属性值,那么就可能出现问题。

因此,在为某属性带入对象前,可以使用相应的方法来验证,但是验证方法不能自动调用(使用Cocoa绑定时,可以设定自动验证),因此,在访问属性前,必须自行调用该方法。

验证某键字符串的属性值的方法可按如下形式定义。下划线中写入键字符串。参数ioValue为需要验证的对象的指针。参数outError被用来当验证结果中存在时返回出错信息。

- (BOOL)validate___:(inoutid*)ioValue error:(outNSError **)outError;

对象有问题时,但是能将对象修正为有效值时,方法会创建新的对象,并取代原对象将新对象带入ioValue。参数outError不变,返回值为YES。

对象有问题且不能修正时,则创建错误对象并将其带入参数outError。方法返回NO。

设定属性访问值的访问器方法(set___:)不能调用验证方法。

运行时,键会被动态地赋值给对象的情况下,不能在代码中使用上述方法名。此时,可以使用下面的两个方法。

- (BOOL)validateValue:(inoutid*)ioValue forKey:(NSString *)key error:(outNSError**)outError

//使用指定键寻找validate___:error:的验证方法并调用,如果不存在这样的验证方法,则返回YES。

- (BOOL)validateValue:(inoutid*)ioValue forKeyPath:(NSString *)keyPath error:(outNSError**)outError

//实际被调用的验证方法并不是该方法的接收器,而是与最后的键元素相对应的属性的验证方法。

键值编码的准则

如果可以使用键值编码来访问某个属性,则称该属性是键值编码的准则,或称为KVC准则(compliance)。反之,如果知道某属性为KVC准则,那么就可以编写使用键值编码的程序。KVC准则和协议适用的概念不同,它不是以类为单位,而是讨论以各个属性为单位是不是准则的问题。

要使某属性为KVC准则,就必须实现能使用valueForKey:方法的访问器。当属性可变时,还需要与方法setValue:forKey:相应的访问器。下面列举一些具体的条件:

property为属性(标量值或单纯型的对象)或一对一关系时,要想成为KVC准则,就需要满足如下条件。属性名为“name”。

1.实现了name或isName访问器方法。或者包含name(或_name)实例变量。

2.可变属性时,还需要实现setName:方法。需要执行键值验证时,要实现验证方法(validateName:error:)。但是,setName:方法中不能调用验证方法。

属性为一对多关系时,要想成为KVC准则。需满足如下条件。属性名为“names”。

1.实现了返回数组的names方法。或者持有包含names(或names)数组对象的实例变量或者实现了带索引的访问器模式的方法countOfNames以及objectInNamesAtIndex

2.当一对多关系的属性可变时

持有返回可变数组对象的names方法。或者

实现了带索引的访问器模式的方法

insertObject:inNamesAtIndex:以及removeObjectFromNamesAtIndex:。

当然,在实现带索引的访问器模式的方法时,为改善执行效率,也可以添加其他方法来实现。

键值观察

键值观察(key-value-observing),即某个对象的属性改变时通知其他对象的机制。有时也记作KVO。

对被视察对象来说,键值观察就是注册想要监视的属性的键路径和观察者。当属性改变时,观察者会接收到消息。

仅仅在使用键值编码准则来访问访问器或实例变量的情况下,才可以监视属性的变化。在方法内直接改变实例变量值时,就不能监视了。

NSObject中提供了键值观察所必需的方法,头文件Foundation/NSKeyValueObserving.h中将其定义为了非正式协议。

使用引用计数管理方式时需要注意一些事项。注册属性监视时,不需要持有观察者及监视对象的属性。还有,如果在不删除注册信息的情况下将关联对象释放,那么随着属性的变更,就可能会发生访问已释放了的对象的危险。

一对多的属性监视

监视方法与前述方法相同。但有一点必须注意,那就是一对多关系为数组类型时使用方法mutableArrayValueForKey:获得对象,集合类型同样如此,如果不修改值是不能被监视的。

依赖键的登记

某属性值伴随着同一对象的其他属性的改变而改变是常有的事情。通过事先将这样的依赖关系在类中注册,那么即使属性值间接的发生了变化,也会发送通知。为此,需要使用下面的类方法:(略)

Cocoa绑定描述

目标-行为-模式的弱点

在面向对象程序设计中,应尽可能地去除特定的类与类之间的关系,定义低耦合的类。也就是说,某个类改变时,最好不会影响其他的类,否则就不是我们期望的编程。但是,就算完美地定义了每个类,它们间如果不能联动也就不能实现功能。一个典型的例子就是,窗体及窗体上面的按钮菜单等GUI组件也是对象,它们间如果不连接,程序就不能运行。

这样看来,除了各个类或GUI组件原本就应该有的功能之外,还需要为它们之间的联动补充必要的代码。而这样的代码就像是把元素粘在一起的胶水,因此称为胶水代码(glue code)。胶水代码既可以被写成专门的类,也可以渗透在各个关联的类中。

Mac OSX从第一个版本NexTstep开始,就有了将GUI组件与使用组件的对象结合在一起的开发工具Interface Builder。“胶”的部分不需要特意编写代码,使用Interface Builder的的GUI环境就可以简单地实现,因此能大幅提高编程效率。Interface Builder中中GUI组件被操作时,会预先指定向哪个对象发送什么消息,这里,Objective-C灵活的消息发送机制发挥着非常大的作用。以上就是我们说明过的目标行为模式。

但是,“从操作组件向目标发送消息”这样的方式在很多情况下都是无能为力的。特别是当值改变时,为了使多个对象联动,必须书写专门的代码。图20-2(a)希望实现的是,绘图用的参数可从滑块或文本域输入中获得,并根据值的变化改变各种显示,这样的情况很常见,但只用Interface Builder连接解决不了这样的问题,还有必要使用专门的对象。程序自身虽然简单,但这样的组合多了的话,就要写大量相似的代码。

什么是Cocoa绑定

从Mac OS X 10.3起开始引入的Cocoa绑定(Cocoa binding)是指,使用键值编码和键值观察的组合,在多个对象间共享属性值的变化的机制。(在iOS中不可以使用)

上图(b)为例说明了它的概要。

Cocoa绑定所需的方法

使用Cocoa绑定,将某对象绑定到控制器属性时,该对象必须实现下面的方法。该方法用头文件AppKit/NSKeyValueBinding.h中的非正式协议NSKeyValueBindingCreation来声明。

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

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,097评论 18 139
  • 国家电网公司企业标准(Q/GDW)- 面向对象的用电信息数据交换协议 - 报批稿:20170802 前言: 排版 ...
    庭说阅读 10,493评论 6 13
  • 开年初八上班就似乎感觉不到年味了,一切工作生活变的日常如初。情人节郭先生送来的玫瑰和生日的三个蛋糕,似乎为这个二月...
    行走的蓝天白云阅读 239评论 0 0
  • 清晨,熹微的光照进墙角的窄条窗,他缓慢醒转,周围的邻居们刚刚入梦,隔壁青铜方壶上的夔龙还用眨眼朝他道早安。这是一天...
    风华布衣阅读 211评论 0 2
  • 吾尝终日而思矣,不如须臾之所学也; 思想上的差异,很多时候是阅历和知识水平的差异。合理、有利于自己发展的三观至关重...
    大二小阅读 419评论 0 0