一个对象通常在interface声明它的property
,这些property
属于以下类别之一:
-
Attributes. 比如基本数据类型,字符串,或者布尔值。值类型比如
NSNumber
和其他不可变类型(如NSColor
)也被视为属性 -
To-one 关系. 这些是具有自己属性的可变对象。一个对象的属性可以在对象本身不变的情况下改变。比如,一个
bankAccount
可能拥有一个owner
属性,该属性是一个Person
对象的实例,owner
有一个address
属性,这个owner
的address
可能改变,而不会更改bankAccount
的owner
的资料。 -
To-many 关系.这些是集合对象,一般是
NSArray
或者NSSet
的实例对象,当然也可以自定义集合类型。
比如list 2-1
描述
list 2-1
@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
我们通常会为interface
的属性提供一些访问方法。或明确地写出这些方法,或者依赖编译器自动生成这些方法。无论哪种方式,使用这些方法的时候必须在编译之前声明这些属性。这些方法的名称会成为代码的静态部分。比如,在表2-1中,声明了一个BankAccount
对象,编译器生成了一个setter方法,可以被myAccount
实例调用
[myAccount setCurrentBalance:@(100.0)];
这是一种很直接的方法,但缺少一些灵活性。KVC提供了一种更加通用的机制(使用字符串标识符)来访问对象的属性。
通过Key和Key Path来标识对象的属性
key
是标识特定属性的字符串。通常,根据约定,标识属性的key
是属性自身的名字。 key
必须是使用ASCII编码,不能含有空格,并且通常使用小写字母开头(也存在例外,比如在许多类中的URL属性.
由于表2-1中的BankAccount
类是支持KVC的,因此它可以识别这些key:owner
,currentBalance
, 和 transactions
,它们都是属性的名称。可以不使用setCurrentBalance:
方法,而是通过key来设置value
[myAccount setValue:@(100.0) forKey:@"currentBalance"];
事实上,我们可以通过相同的方法传递不同的参数key来设置myAccount
对象的所有属性。因为参数是个字符串,所以可以作为变量在运行时进行操作或者修改。
key path
是一个以圆点隔开的字符串,用来指定要遍历的对象属性。在序列中的第一个key
的属性是相对于receiver
的,后续的key
都相对于前一个属性的值进行评估。key path
对于用单个方法深入到对象的层次结构非常有用。
比如,owner.address.street
路径指就是存储在银行账号的所有者地址中的street
字符串的值,假设Person
和Address
类也是支持KVC的.
使用key获取属性值
当对象采用了NSKeyValueCoding
协议时就会支持KVC。NSObject
提供了该协议的必要方法的默认实现,所以当一个继承自NSObject
的对象,会自动遵守这个协议。并且至少实现了下面的基于key
的getter方法
-
valueForKey: 返回名字为参数
key
的属性的值。如果根据访问查询规则没有找到key
名称的属性,会向自身发送一个valueForUndefinedKey:
消息。valueForUndefinedKey:
默认会抛出NSUndefinedKeyException
异常,但子类可以实现该方法做一些更合适的操作. -
valueForKeyPath: 返回receiver的指定路径的值。在
key path
序列中任何不支持kvc的对象(即valueForKey:
的默认实现不能找到访问方法)都会收到valueForUndefinedKey:
消息。[object valueForKeyPath:@"a.b.c"]
等效于 [[[object valueForKey:@"a"] valueForKey:@"b"] valueForKey:@"c"] -
dictionaryWithValuesForKeys: 通过数组
keys
(存放的都是key
),为receiver返回一个字典。这个方法为数组keys
中每个key
调用valueForKey:
方法,然后返回一个字典,字典中包含了数组中所有key对应的value。
需要注意的是,集合对象,比如
NSArray, NSSet, NSDictionary
,不能含有nil。可以使用NSNull
对象来表示nil.dictionaryWithValuesForKeys:
以及与其相关的setValuesForKeysWithDictionary:
方法的默认实现是:自动在NSNull
(在字典的参数中)和nil(在存储的属性中)之间进行转换
使用key设置属性值
与getter一样,支持KVC的对象同样提供了一组基于NSObject
中NSKeyValueCoding
协议实现的默认行为的通用setter。
-
setValue:forKey: 将
receiver
的key
属性的值设置为给定的value
.setValue:forKey:
方法的默认实现会自动将表示基本数据变量和结构体的NSNumber
和NSValue
对象解包(解包为基本数据类型或者结构体),并将它们赋值给属性。如果收到setter调用的对象的属性没有指定的
key
,对象会给自己发送一个setValue:forUndefinedKey:
消息,setValue:forUndefinedKey:
默认会抛出NSUndefinedKeyException
异常。 -
setValue:forKeyPath: 将给定的值设置给reciver的指定路径。在
key path
序列中有key不支持kvc的话会收到setValue:forUndefinedKey:
消息。 -
setValuesForKeysWithDictionary: 使用字典中的
key
来标识属性,将receiver
的属性设置为字典中的value
.该方法的默认实现是为每个键值对调用setValue:forKey:
(根据需要将nil
替代为NSNull
)Person *per = [[Person alloc] init]; per.age = 18; BankAccount *account = [[BankAccount alloc] init]; NSDictionary *dict = @{@"currentBalance":@(20), @"owner":per, }; [account setValuesForKeysWithDictionary:dict]; NSLog(@"account: %@", account);
当想要给一个非对象属性设置一个
nil
值时,kvc对象会给自己发送一个setNilValueForKey:
消息,setNilValueForKey:
默认会抛出NSInvalidArgumentException
异常,但是对象可以覆盖该行为,以替换默认值或标记值。
访问集合属性
KVC对象以与公开它的其他属性同样的方式公开它的to-many
属性。可以使用valueForKey:
或setValue:forKey:
(或者其他等同于keypath的路径)get或set一个集合属性,但是,当想要操作集合里的内容时,使用协议定义的可变代理方法通常是最有效率的。
协议为集合对象的访问定义了三种不同的代理方法,每一种方法都有一个key
和keyPath
.
-
mutableArrayValueForKey:
和mutableArrayValueForKeyPath:
这些方法返回一个类似于NSMutableArray
的代理对象。 -
mutableSetValueForKey:
和mutableSetValueForKeyPath:
这些方法返回一个类似于NSMutableSet
的代理对象。 -
mutableOrderedSetValueForKey:
和mutableOrderedSetValueForKeyPath:
返回一个类似于NSMutableOrderedSet
的对象。
当你对该代理对象进行添加对象、 删除对象、 或者插入对象操作时,协议的默认实现会修改相应的底层属性。这比通过valueForKey:
方法获取一个不可变的集合属性更加有效率,通过修改过的内容创建一个修改的对象,然后通过setValue:forKey:
将其存储回对象。许多情况下,它比直接使用可变属性更加有效。这些方法更有利于集合中对象的KVO。
网友评论