Accessing Object Properties

对象通常在接口声明中指定属性,这些属性属于以下几个类别之一:

  • 属性 这些是简单值,例如标量,字符串或布尔值。 诸如NSNumber之类的值对象和诸如NSColor之类的其他不可变类型也被视为属性。
  • 对1关系。这些是具有自己属性的可变对象。 对象的属性可以更改,而对象本身不会更改。 例如,银行帐户对象可能具有所有者属性,该属性是Person对象的实例,该对象本身具有地址属性。 所有者的地址可能会更改,而不会更改银行帐户持有的所有者参考。 银行帐户的所有者没有变更。 只有她的地址。
  • 对多关系。这些是集合对象。你通常用NSArray或者NSSet对象去持有这个一个集合,自定义的集合类也是有可能的。

清单2-1中声明的BankAccount对象演示了每种类型的属性之一。

清单 2-1 (BankAccount对象的属性)

@interface BankAccount : NSObject
 
@property (nonatomic) NSNumber* currentBalance;              // An attribute
@property (nonatomic) Person* owner;                         // A to-one relation
@property (nonatomic) NSArray< Transaction* >* transactions; // A to-many relation
 
@end

为了保持封装,对象通常为其接口上的属性提供访问器方法。 对象的作者可以显式地编写这些方法,也可以依赖编译器自动合成它们。 无论哪种方式,使用这些访问器之一的代码的作者必须在编译之前将属性名称写入代码中。 访问器方法的名称成为使用它的代码的静态部分。 例如,给定清单2-1中声明的银行帐户对象,编译器会合成一个可以为myAccount实例调用的setter:

[myAccount setCurrentBalance:@(100.0)];

这是直接的,但缺乏灵活性。 另一方面,符合键值编码的对象提供了使用字符串标识符访问对象属性的更通用的机制。

使用键和键路径标识对象的属性

键是标识特定属性的字符串。 通常,按照惯例,表示属性的键是代码中显示的属性本身的名称。 密钥必须使用ASCII编码,可能不包含空格,并且通常以小写字母开头(尽管有例外,例如在许多类中找到的URL属性)。
因为清单2-1中的BankAccount类是符合键值编码的,所以它识别键所有者currentBalancetransactions,它们是其属性的名称。 您可以通过其键设置值,而不是调用setCurrentBalance:方法:

[myAccount setValue:@(100.0) forKey:@"currentBalance"];

实际上,您可以使用不同的 key 参数,使用相同的方法设置myAccount对象的所有属性。 因为参数是字符串,所以它可以是在运行时操作的变量。

键路径是一串点分隔键,用于指定要遍历的对象属性序列。 序列中第一个键的属性是相对于接收者的,并且每个后续键相对于前一个属性的值进行评估。 关键路径对于使用单个方法调用向下钻取到对象层次结构非常有用。

例如,应用于银行帐户实例的密钥路径owner.address.street是指存储在银行帐户所有者地址中的街道字符串的值,假设PersonAddress类也符合键值编码。

NOTE

在Swift中,您可以使用#keyPath表达式,而不是使用字符串来指示键或键路径。 这提供了编译时检查的优点,如Using Swift with Cocoa and Objective-C (Swift 3) guide的Keys and Key Paths部分所述。

Getting Attribute Values Using Keys

对象在采用NSKeyValueCoding协议时符合键值编码。 从NSObject继承的对象(提供协议基本方法的默认实现)会自动采用此协议的某些默认行为。 这样的对象至少实现了以下基于 key 的基本getter:

  • valueForKey: - 返回由键参数命名的属性的值。如果无法根据 Accessor Search Patterns中描述的规则找到由密钥命名的属性,则对象向自己发送一个 valueForUndefinedKey: 消息。valueForUndefinedKey: 的默认实现:引发一个NSUndefinedKeyException,但子类可能会重写此行为并更优雅地处理此情况.
  • valueForKeyPath: - 返回指定键路径相对于接收器的值。键路径序列中的任何对象,如果不符合特定键的键值编码,即,对于该键,valueForKey:的默认实现找不到访问器方法,则会收到valueforundefinedkey:message。
  • dictionaryWithValuesForKeys: - 返回相对于接收器的键数组的值。方法为数组中的每个键调用ValueForkey:。返回的NSDictionary包含数组中所有键的值。

注意

集合对象(如NSArray,NSSet和NSDictionary)不能包含nil作为值。 相反,您使用NSNull对象表示nil值。 NSNull提供了一个表示对象属性的nil值的实例。 dictionaryWithValuesForKeys的默认实现:和相关的setValuesForKeysWithDictionary:自动在NSNull(在字典参数中)和nil(在存储属性中)之间进行转换。

当您使用键路径来寻址属性时,如果键路径中的最后一个键是多对多关系(即它引用一个集合),则返回的值是一个包含键的所有值的集合 在to-many键的右侧。 例如,请求键路径transactions.payee的值返回一个包含所有事务的所有收款人对象的数组。 这也适用于密钥路径中的多个数组。 密钥路径accounts.transactions.payee返回一个数组,其中包含所有帐户中所有交易的所有收款人对象。

Setting Attribute Values Using Keys

getters一样,键值编码兼容对象也提供了一小组具有默认行为的通用setters,这些默认行为基于NSObject中的NSKeyValueCoding协议的实现:

  • setValue:forKey: - 将 key 指定的的对象设置为给定值。setValue:forkey:的默认实现是自动解包表示标量和结构体的 NSNumber 和 NSValue 对象,并将它们分配给属性。有关 wrapping 和 unwrapping 的详细信息,请参见Representing Non-Object Values
    若指定的 key 不存在,对象会向自己发送setValue:forUndefinedKey: 消息。setValue:forUndefinedKey:的默认实现是抛出NSUndefinedKeyException异常。当然,子类可以重写此方法。
  • setValue:forKeyPath: - KeyPath上的任何 key 不兼容, 都会调用setValue:forUndefinedKey:.
  • setValuesForKeysWithDictionary: - 默认的实现是为每一个 key-value 对调用setValue:forKey:, 在需要的情况下会将 nil 转换为 NSNull 对象。

默认的实现中,当你尝试为一个非对象属性设定 nil 时,方法调用者会受到一个setNilValueForKey:消息。此消息的默认实现是抛出一个 NSInvalidArgumentException, 子类可重写此方法, 使用默认值或者 marker 来代替, 就像 Handling Non-Object Values 中描述的那样.

Using Keys to Simplify Object Access

要了解基于键的getter和setter如何简化代码,请考虑以下示例。在MacOS中,InstableView和nsOutlineView对象将标识符字符串与其每列关联。如果支持该表的模型对象不符合键值编码,则该表的数据源方法将被强制依次检查每个列标识符,以找到要返回的正确属性,如清单2-2所示。此外,在将来,当您向模型中添加另一个属性(在本例中是Person对象)时,还必须重新访问数据源方法,添加另一个条件来测试新属性并返回相关值。

Listing 2-2 Implementation of data source method without key-value coding

- (id)tableView:(NSTableView *)tableview objectValueForTableColumn:(id)column row:(NSInteger)row
{
    id result = nil;
    Person *person = [self.people objectAtIndex:row];
 
    if ([[column identifier] isEqualToString:@"name"]) {
        result = [person name];
    } else if ([[column identifier] isEqualToString:@"age"]) {
        result = @([person age]);  // Wrap age, a scalar, as an NSNumber
    } else if ([[column identifier] isEqualToString:@"favoriteColor"]) {
        result = [person favoriteColor];
    } // And so on...
 
    return result;
}

另一方面,清单2-3显示了同一数据源方法的更紧凑的实现,该方法利用键值编码兼容的Person对象。数据源方法只使用valueforkey:getter,使用列标识符作为键返回适当的值。除了较短之外,它也更通用,因为在以后添加新列时,只要列标识符始终与模型对象的属性名匹配,它将继续保持不变的工作。

Listing 2-3Implementation of data source method with key-value coding

- (id)tableView:(NSTableView *)tableview objectValueForTableColumn:(id)column row:(NSInteger)row
{
    return [[self.people objectAtIndex:row] valueForKey:[column identifier]];
}

猜你喜欢

转载自blog.csdn.net/qfeung/article/details/86933802
今日推荐