Iphone开发基础篇(十三)-ObjectC之键/值编码(KVC)

来源:互联网 发布:js 判断正整数 编辑:程序博客网 时间:2024/05/18 13:45

什么是键-值编码?

键-值编码是一个用于间接访问对象属性的机制,使用该机制不需要调用存取方法和变量实例就可访问对象属性。本质上讲,键-值编码定义了您的程序存取方法需要实现的样式及方法签名。

存取方法,如其名称所示,提供对应用程序内数据模型的属性值的存取。存取方法的基本形式有两种:

获取型存取方法和设定型存取器方法。获取型存取器方法的基本动作是获取属性的值,相同地,设定型存取方法可以设定属性的值。与此同时还有各种获取器和设定器变种应对对象属性和对多关系。

在您的应用程序中实现键-值编码兼容性是一项重要的设计原则。存取方法可以帮助您加强合适的数据封装,隔离内存管理至集中的地址,简化与其他编程技术的集成比如键-值观察,Core Data,Cocoa绑定和脚本编写。键-值编码方法在多数情况下可简化程序代码。请查看模型对象实现导览了解更多实现键-值兼容存取方法带来的益处,以及具体的实现方法。
必要的键-值编码方法在Objective-C非标准协议NSKeyValueCoding中被声明,默认的实现方法由NSObject提供。

键-值编码支持带有对象值的属性,同时也支持纯数值类型和结构。非对象参数和返回类型会被识别并自动封装/解封,

键-值编码的基本原理

本文重点阐述了键-值编码的基本原理。

本部分包含如下主要内容:

键和键路径 
使用键-值编码获取属性值 
使用键-值编码设定属性值 
点语法与键-值编码 


键和键路径

键是一个标示对象某个属性的字符串。通常,一个键与接收对象的一个访问器方法或是实例变量对应。键必须使用ASCII编码,以小写字母开头,并且不能包含空格。

例如,键可以是payee, openingBalance, transactionsamount

键路径是一串由点隔开的键,用于指定一组对象属性序列。序列中第一个键的属性与接收者相关,其他后续键则是相对于前一个属性的值。

比如,键路径address.street将会从接收方对象获得address属性的值,之后依据address对象确定street的属性。

使用键-值编码获取属性值

方法valueForKey:返回接收对象特定键的值。如果该特定键没有对应的访问器或者实例变量,接收者为自己发送一个valueForUndefinedKey:消息。默认的valueForUndefinedKey:实现方法是增加一个 NSUndefinedKeyException;;需要说明的是,子类可以重载这个动作。

类似地,valueForKeyPath:返回接收对象特定键路径的值。在键路径序列中任何不遵循键-值编码的对象,都会收到一个valueForUndefinedKey:消息。

方法dictionaryWithValuesForKeys:得到接收对象的一组键的值。被返回的NSDictionary包含了数组中所有键的值。

注解:集合对象无法包含nil作为其具体值,如NSArrayNSSetNSDictionary。相应地,nil值用一个特定的对象NSNull来表示NSNull提供了一个单一实例用于表示对象属性中的的nil值。默认的实现方法中,dictionaryWithValuesForKeys:setValuesForKeysWithDictionary:自动地将NSNullnil相互转换,因此您的对象不需要进行NSNull的测试操作。

当包含有对多关系属性键,且该键不是路径中的最后一个键的键路径被返回时,返回的值将是一个包含了在这个对多键右侧所有键的值的集合。例如,对键路径transactions.payee 的值请求将返回一个包含了全部交易的全部payee对象的数组。这同样可用于键路径中的多个数组。键路径accounts.transactions.payee将返回所有帐号中全部交易的全部payee对象。

使用键-值编码设定属性值

方法setValue:forKey:用于将接收对象的特定键设定为所提供的值。缺省的实现中,setValue:forKey:自动地将代表数值和结构的NSValue 对象解封,并将它们关联到属性。具体的封装和解封定义请参照“数值与结构的支持”

如果指定的键不存在,一个setValue:forUndefinedKey:消息将会发送给接收对象。默认的情况下,setValue:forUndefinedKey:将增加一个NSUndefinedKeyException;不过,子类可以重载这个方法以便在定制方式中处理请求。

方法setValue:forKeyPath:行为上与其类似,但该方法既可以处理键路径,也可以处理单独的键。

最后,setValuesForKeysWithDictionary:使用指定字典中的具体值设定接收对象的属性,并使用字典键来识别这些属性。默认的方法是为每一对键-值调用setValue:forKey:,并按需要将nil替换为NSNull 对象。

另一个您需要考虑的情况则是,当给非对象属性设定nil值时会发生什么情况。这时候,接收对象会给自身发送一个setNilValueForKey:消息。默认的情况下,setNilValueForKey:引起一个NSInvalidArgumentException。您的应用程序可以重载这个方法来将其替换为一个默认值或者是标记值,然后使用新值调用setValue:forKey:。

点语法与键-值编码

Objective-C 2.0中的点方法和键-值编码是两个相互垂直的技术。无论是否使用点方法,您都可以使用键-值编码,反之亦然。当然您也可以两种方法都采用。在使用键-值编码时,点方法被用于为键路径中的元素定界。请记住,当使用点方法访问属性时,您必须调用接收者的标准访问器方法。

您可以使用键-值编码方法来访问一个属性,如下所示代码给出了一个类:

@interface MyClass
@property NSString *stringProperty;
@property NSInteger integerProperty;
@property MyClass *linkedInstance;
@end

您可以使用KVC来访问实例中的属性:

MyClass *myInstance = [[MyClass alloc] init];
NSString *string = [myInstance valueForKey:@"stringProperty"];
[myInstance setValue:[NSNumber numberWithInt:2] forKey:@"integerProperty"];

为了明显区分点语法和键值编码键路径,请参考以下代码。

MyClass *anotherInstance = [[MyClass alloc] init];
myInstance.linkedInstance = anotherInstance;
myInstance.linkedInstance.integerProperty = 2;

这与下面的运行结果是相同的:

MyClass *anotherInstance = [[MyClass alloc] init];
myInstance.linkedInstance = anotherInstance;
[myInstance setValue:[NSNumber numberWithInt:2]
            forKeyPath:@"linkedInstance.integerProperty"];

使用键-值编码来简化代码

您可以在自己的代码中使用键-值编码方法来统一实现方法。例如NSTableViewNSOutlineView对象均为其每一列关联了一个标识符字串。借助使这个标识符作为标示您希望显示的属性的键,代码简化将会变得异常轻松。

表1展示了一个不使用键-值编码的NSTableView代理方法实现途径。表2则展示了一个采用键-值编码来借助列标识符作为键返回合适值的实现途径。

表1  不使用键-值编码的数据-源实现方法

- (id)tableView:(NSTableView *)tableview
      objectValueForTableColumn:(id)column
                            row:(int)row
{
    ChildObject *child = [childrenArray objectAtIndex:row];
    if ( [[column identifier] isEqualToString:@"name"] ) {
        return [child name];
    }
    if ( [[column identifier] isEqualToString:@"age"] ) {
        return [child age];
    }
    if ( [[column identifier] isEqualToString:@"favoriteColor"] ) {
        // etc...
    }
    // etc...
}

表2  使用键-值编码的数据源实现方法

- (id)tableView:(NSTableView *)tableview
      objectValueForTableColumn:(id)column
                            row:(int)row
{
    ChildObject *child = [childrenArray objectAtIndex:row];
    return [child valueForKey:[column identifier]];
}