官方文档
An object typically specifies properties in its interface declaration, and these properties belong to one of several categories:
一个对象通常在interface声明中指定属性,并且这些属性属于几个类别中的一:
- Attributes. These are simple values, such as a scalars, strings, or Boolean values. Value objects such as
NSNumber
and other immutable types such as asNSColor
are also considered attributes.
- 属性。这些是简单的值,例如一个标量,字符串或者布尔值。值对象比如
NSNumber
和其他不可变类型例如NSColor
都被认为是属性。
- To-one relationships. These are mutable objects with properties of their own. An object’s properties can change without the object itself changing. For example, a bank account object might have an owner property that is an instance of a
Person
object, which itself has an address property. The owner’s address may change without changing the owner reference held by the bank account. The bank account’s owner did not change. Only her address did.
- To-one关系。这些是具有自己属性的可变对象。对象的属性会更改而对象自己不用改变。例如,一个银行账户对象可能有一个拥有者属性是一个
Person
对象的实例,这个属性自己有一个地址属性。所有者的地址可能改变而不用改变银行账户持有的所有者引用。银行账户的所有者没有改变。只有它的地址改变了。
- To-many relationships. These are collection objects. You commonly use an instance of
NSArray
orNSSet
to hold such a collection, although custom collection classes are also possible.
- To-many关系。这些是集合对象。通常使用一个
NSArray
或NSSet
的实例来持有这样的集合,尽管自定义集合类也是可以的。
Listing 2中的BankAccount对象演示了每种属性的类型之一。
Listing 2-1Properties of the BankAccount object
@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
In order to maintain encapsulation, an object typically provides accessor methods for the properties on its interface. The object’s author may write these methods explicitly or may rely on the compiler to synthesize them automatically. Either way, the author of code using one of these accessors must write the property name into the code before compiling it. The name of the accessor method becomes a static part of the code that is using it. For example, given the bank account object declared in Listing 2-1, the compiler synthesizes a
setter
that you can invoke for themyAccount
instance:
为了保持封装性,对象通常在interface中提供属性的访问器方法。对象的作者可以显式的编写这些方法或者依赖编译器来自动生成它们。无论哪种方式,代码的作者在编译之前必须使用这些访问器之一把属性名称写到代码中去。访问器方法的名称成为使用它的代码的一个静态部分。例如,给定Listing 2-1声明的银行账户对象,编译器可以合成一个myAccount
实例可以调用的setter:
[myAccount setCurrentBalance:@(100.0)];
This is direct, but lacks flexibility. A key-value coding compliant object, on the other hand, offers a more general mechanism to access an object’s properties using string identifiers.
这是直接的,但是缺乏灵活性。另一方面,符合KVC的对象提供了一种使用字符串标识符来访问对象属性的更通用的机制。
使用key和key path来标识对象属性(Identifying an Object’s Properties with Keys and Key Paths)
A key is a string that identifies a specific property. Typically, by convention, the key representing a property is the name of the property itself as it appears in code. Keys must use
ASCII
encoding, may not contain whitespace, and usually begin with a lowercase letter (though there are exceptions, such as the URL property found in many classes).
key是一个标识特定属性的字符串。通常,按照惯例,表示属性的key是属性在代码中显示的属性本身的名称。key必须使用ASCII
编码,可能不包含空格,通常以小写字母开头(尽管有例外,例如在很多类中的URL属性)。
Because the
BankAccount
class in Listing 2-1 is key-value coding compliant, it recognizes the keys owner,currentBalance
, andtransactions
, which are the names of its properties. Instead of calling thesetCurrentBalance:
method, you can set the value by its key:
因为BankAccount
类是符合KVC的,它能识别key的拥有者,currentBalance
和transactions
时它的属性的名称。你可以通过它的key来设置值,而不用调用setCurrentBalance:
[myAccount setValue:@(100.0) forKey:@"currentBalance"];
In fact, you can set all the properties of the myAccount object with the same method, using different key parameters. Because the parameter is a string, it can be a variable that is manipulated at run-time.
事实上,你可以用相同的方法使用不同的key参数来设置myAccount对象的所有属性。因为参数是一个字符串,它可以是在运行时操作的变量。
A key path is a string of dot-separated keys used to specify a sequence of object properties to traverse. The property of the first key in the sequence is relative to the receiver, and each subsequent key is evaluated relative to the value of the previous property. Key paths are useful for drilling down into a hierarchy of objects with a single method call.
key path是一个用点分隔的key组成的字符串,用于指定要遍历的对象属性序列。序列中的第一个key是相对于接收者的,并且每一个后续的key相对于前一个属性的值进行评估。key paths对使用单个方法调用到向下钻取到对象层次非常有用。
For example, the key path
owner.address.street
applied to a bank account instance refers to the value of the street string that is stored in the address of the bank account’s owner, assuming thePerson
andAddress
classes are also key-value coding compliant.
例如,应用于银行账户实例的key path owner.address.street
是指存储在银行账户所有者的地址中的街道字符串的值,假设Person
和Address
类都是符合KVC的。
NOTE
In Swift, instead of using a string to indicate a key or key path, you can use a #keyPath expression. This offers the advantage of a compile time check, as described in the Keys and Key Paths section of the Using Swift with Cocoa and Objective-C (Swift 3) guide.
注意:在Swift中你可以使用#keyPath表达式来表示一个key或者key path,而不是用字符串。这提供了编译时检查的有点,如Using Swift with Cocoa和Objective-C (Swift 3) guide中的Keys and Key Paths部分所述。
使用key获取属性值(Getting Attribute Values Using Keys)
An object is key-value coding compliant when it adopts the
NSKeyValueCoding
protocol. An object that inherits from NSObject, which provides a default implementation of the protocol’s essential methods, automatically adopts this protocol with certain default behaviors. Such an object implements at least the following basic key-based getters:
当对象采用了 NSKeyValueCoding
协议它就是符合KVC的。继承自 NSObject(它提供了这个协议的基本方法的默认实现)的对象通过某些默认的行为自动采用这个协议。这样的对象至少实现了以下基本的基于key的getters:
- valueForKey: - Returns the value of a property named by the key parameter. If the property named by the key cannot be found according to the rules described in Accessor Search Patterns, then the object sends itself a valueForUndefinedKey: message. The default implementation of valueForUndefinedKey: raises an
NSUndefinedKeyException
, but subclasses may override this behavior and handle the situation more gracefully.
- valueForKey: --返回以key参数命名的属性的值。如果以key命名的属性通过Accessor Search Patterns中描述的规则无法找到,然后对象会给自身发送一个valueForUndefinedKey:消息。valueForUndefinedKey:的默认实现会引发
NSUndefinedKeyException
,但是子类可以覆盖这个行为并且更优雅地处理这个情况。
- valueForKeyPath: - Returns the value for the specified key path relative to the receiver. Any object in the key path sequence that is not key-value coding compliant for a particular key—that is, for which the default implementation of
valueForKey:
cannot find an accessor method—receives a valueForUndefinedKey: message.
- valueForKeyPath: -- 返回相对于接收者指定的key path的值。key path序列中的任何对象对一个特定的key都不符合KVC,即对任何对象
valueForKey:
的默认实现都不能找到一个访问器方法,那这些对象都会收到一个 valueForUndefinedKey: 消息。
- dictionaryWithValuesForKeys: - Returns the values for an array of keys relative to the receiver. The method calls
valueForKey:
for each key in the array. The returnedNSDictionary
contains values for all the keys in the array.
- dictionaryWithValuesForKeys: -返回相对于接收者的key数组对应的值。这个方法为数组中的每个key调用
valueForKey:
。返回的NSDictionary
包含了数组中所有key对应的值。
NOTE
Collection objects, such asNSArray
,NSSet
, andNSDictionary
, can’t containnil
as a value. Instead, you representnil
values using theNSNull
object.NSNull
provides a single instance that represents the nil value for object properties. The default implementations ofdictionaryWithValuesForKeys:
and the relatedsetValuesForKeysWithDictionary:
translate betweenNSNull
(in the dictionary parameter) andnil
(in the stored property) automatically.
注意 集合对象,例如NSArray
,NSSet
和NSDictionary
都不能包含nil
作为值。相反,可以使用NSNull
表示nil
值。NSNull
提供了一个单独的接口表示对象属性的nil
值dictionaryWithValuesForKeys:
和相关的setValuesForKeysWithDictionary:
的默认实现在NSNull
(在字典参数中)和nil
(在存储的属性中)之间自动转换。
When you use a key path to address a property, if any but the final key in the key path is a to-many relationship (that is, it references a collection), the returned value is a collection containing all the values for the keys to the right of the to-many key. For example, requesting the value of the key path
transactions.payee
returns an array containing all thepayee
objects for all thetransactions
. This also works for multiple arrays in the key path. The key pathaccounts.transactions.payee
returns an array with all thepayee
objects for all thetransactions
in all theaccounts
.
当你使用key path来寻址一个属性时,如果key path中的最后一个key是to-many关系(即它引用一个集合),则返回值是一个集合,这个集合包含to-many关系key的右侧所有key的值。例如,请求key path transactions.payee
的值返回一个包含所有transactions
的所有payee
对象的数组。这也适用于key path中的多个数组。key path accounts.transactions.payee
返回一个包含所有accounts
的所有transactions
的所有payee
对象的数组。
用键来设置属性值(Setting Attribute Values Using Keys)
As with getters, key-value coding compliant objects also provide a small group of generalized setters with default behavior based upon the implementation of the
NSKeyValueCoding
protocol found in NSObject:
和getters一样,符合kvc的对象也提供了一小组具有默认行为的通用setter,这些行为基于 NSObject中的NSKeyValueCoding
协议的实现:
- setValue:forKey: - Sets the value of the specified key relative to the object receiving the message to the given value. The default implementation of
setValue:forKey:
automatically unwrapsNSNumber
andNSValue
objects that represent scalars and structs and assigns them to the property. See Representing Non-Object Values for details on the wrapping and unwrapping semantics.
If the specified key corresponds to a property that the object receiving the setter call does not have, the object sends itself a setValue:forUndefinedKey: message. The default implementation ofsetValue:forUndefinedKey:
raises anNSUndefinedKeyException
. However, subclasses may override this method to handle the request in a custom manner.
- setValue:forKey: -将相对于接收消息的对象的指定键设置为给定值。
setValue:forKey:
的默认实现会自动解包表示标量和结构体的NSNumber
和NSValue
对象来并将他们分配给属性。有关包装和解包语义的详细信息,请参阅Representing Non-Object Values。
如果指定的键对应于接收setter调用的对象没有的属性,这个对象会给自身发送一个setValue:forUndefinedKey:消息。setValue:forUndefinedKey:
的默认实现会引发NSUndefinedKeyException
。然而,子类可能重写这个方法来以一种自定义的方式来处理请求。
- setValue:forKeyPath: - Sets the given value at the specified key path relative to the receiver. Any object in the key path sequence that is not key-value coding compliant for a particular key receives a setValue:forUndefinedKey: message.
- setValue:forKeyPath: - 相对于接收者的指定的key path设置给定的值。对于一个特殊的key不符合kvc的key path序列中的任何对象会受到一个 setValue:forUndefinedKey: 消息。
- setValuesForKeysWithDictionary: - Sets the properties of the receiver with the values in the specified dictionary, using the dictionary keys to identify the properties. The default implementation invokes
setValue:forKey:
for each key-value pair, substitutingnil
forNSNull
objects as required.
- setValuesForKeysWithDictionary: - 用指定字典中的值来设定接收者的属性,并使用字典的键来标识属性。对每个键值对默认实现调用
setValue:forKey:
,并根据需要用nil
替换NSNull
对象。
In the default implementation, when you attempt to set a non-object property to a
nil
value, the key-value coding compliant object sends itself a setNilValueForKey: message. The default implementation ofsetNilValueForKey:
raises an NSInvalidArgumentException, but an object may override this behavior to substitute a default value or a marker value instead, as described in Handling Non-Object Values.
在默认实现中,当你尝试设置一个非对象属性为nil值时,符合KVC的对象会给自身发送一个 setNilValueForKey:消息。setNilValueForKey:
的默认实现会引发NSInvalidArgumentException,但是对象可能覆盖这个行为来替换默认值或标记值,如Handling Non-Object Values所述。
使用键简化对象访问(Using Keys to Simplify Object Access)
To see how key-based getters and setters can simplify your code, consider the following example. In macOS, NSTableView and NSOutlineView objects associate an identifier string with each of their columns. If the model object backing the table is not key-value coding compliant, the table’s data source method is forced to examine each column identifier in turn to find the correct property to return, as shown in Listing 2-2. Further, in the future, when you add another property to your model, in this case the
Person
object, you must also revisit the data source method, adding another condition to test for the new property and return the relevant value.
要想知道基于键的 getters 和 setters 如何简化你的代码,考虑一下以下的例子。在macos中,NSTableView 和 NSOutlineView对象用一个标识字符串来关联它们的每一列。如果支持表的模型对象不符合KVC,则表的数据源方法将被强制检查每一列的标识以查找要返回的正确属性,如Listing 2-2所展示的一样,在未来,当你给你的模型(在本例中为Person对象)添加另一个属性时,还必须重新访问数据源方法,添加另一个条件来测试新的属性并返回相关的值。
Listing 2-2Implementation 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;
}
On the other hand, Listing 2-3 shows a much more compact implementation of the same data source method that takes advantage of a key-value coding compliant
Person
object. Using only the valueForKey: getter, the data source method returns the appropriate value using the column identifier as a key. In addition to being shorter, it is also more general, because it continues to work unchanged when new columns are added later, as long as the column identifiers always match the model object’s property names.
另一方面,Listing 2-3展示了相同数据源方法的更紧凑的实现,该方法利用了符合KVC的Person
对象。仅使用 valueForKey:,数据源方法使用列标识符作为key返回合适的值。除了更短,还更通用,因为在以后添加新列时它能继续保持不变,只要列标识符总是和模型对象属性名称匹配即可。
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]];
}