Key-Value Coding Programming Guide: Accessing Object Properties 这篇文档的翻译,关于KVC的访问原理中的查找模式可以直接查看2.6部分Accessor Search Pattern
一、Key-Value Coding 入门
1.1 关于Key-Value Coding
Key-Value Coding是一种机制, 依靠NSKeyValueCoding
这个非正式协议, 对象采用提供对其属性的非直接访问方式。当一个对象符合key-value编码规范时, 他的属性可以通过简洁、统一的消息接口, 靠字符串参数进行寻址。这种间接访问机制补充了实例变量和实例变量访问方法直接访问。
我们常常使用访问方法去访问一个对象的属性。一个getter返回属性的值。一个setter设置属性的值。在Ojective-C, 我们也可以直接访问属性的潜在的实例变量。以上任何方式都是直接的, 但是需要使用属性特定方法或者知道变量名。当属性列表增加或改变的时候, 它的属性相关访问代码也需要发生变化。相比之下, 键值对编码规定了对象提供一个可以供它的所有属性修改的不变的消息接口。
键值对编码是许多其他Cocoa技术的基础, 比如key-value obeserving, Cocoa bindings, Core Data和AppleScript-ablity。键值对编码也会在某些情况下帮助简化我们的代码。
1.2 使用Key-Value Coding规范对象
直接或者间接继承自NSObject的对象通常采用了key-value coding, 遵循NSKeyValueCoding协议, 提供了对重要方法的默认实现。这样的对象允许其他对象通过一个小的消息接口做到以下的事情:
- 访问对象属性 协议指定了一些方法, 比如通用的getter
valueForKey:
和通用的settersetValue:forKey:
, 通过对象属性名或者key的string参数的形式去访问对象属性。默认的这些方法实现和相关方法通过key去定位和影响下划线数据 - 操控集合属性 默认的访问方法实现在处理一个对象的集合类型属性(比如数组)的时候, 就和处理其他的属性一样。此外,如果一个对象对于一个属性定义了集合访问方法, 它允许键值对访问集合的内容。这通常比直接访问更有效率, 而且允许你通过标准化接口处理常规集合对象。
- 在集合对象上调用集合运算符 当访问一个符合键值对编码规范对象的属性的时候, 你可以在key的string中插入集合的操作符号。集合操作符调用默认的
NSKeyValueCoding
的getter实现去操作集合对象, 返回一个新的、过滤过后的集合对象, 或者是集合对象中代表某个特征的值 - 访问非对象的属性 默认的协议的实现, 会检测到非对象属性, 包括标量和结构体, 然后自动将其封装或展开变成符合协议接口的对象。此外, 协议声明了一个方法, 允许一个规范对象在set中设置非对象属性为nil的时候,通过键值对编码的接口,采取合适的行动。
- 通过key path访问属性 当你存在一个树形结构的符合键值对编码规范的对象链时, 你可以在一次简单的调用中,通过建立在方法调用上的key path, 沿着这个链获取或者设置一个值。
1.3 对于一个对象采用键值对编码
为了确保你自己的对象符合键值对编码规范, 你需要确保它们遵循了NSKeyValueCoding:
非正式协议, 同时实现了相应的方法, 比如通用的gettervalueForKey:
和通用的settersetValue:forKey
。幸运的是, NSObject如上述所说遵循这个协议并提供了这些默认的实现和其他重要方法的实现。因此, 如果你的类是NSObject的派生类, 大部分的工作都完成了。
为了让默认的方法发挥作用, 你要确保你的对象的访问方法和实例变量满足特定的格式。这样能够保证默认的方法实现找到你的对象的属性响应键值对编码的消息。你可以通过提供方法,自己去扩展和自定义处理某些特定的情况, 验证键值对编码。
1.4 其他依赖于键值对编码的Cocoa技术
一个符合键值对编码规范的对象,可以用于大范围的基于这种访问方式的其他Cocoa技术, 包括:
- Key-value observing。 这种机制允许对于一个对象的属性的变化, 另一个对象注册异步通知, Introduction to Key-Value Observing Programming Guide (apple.com)
- Cocoa bindings. 这种技术的集合完全实现了一个Model-View-Controller的范例, 模型封装应用的数据, 视图展示和编辑数据, 控制器调解模型和数据, Introduction to Cocoa Bindings Programming Topics (apple.com)
- Core Data。这个框架为和对象生命周期及对象图管理(包括持久化)有关的任务,提供了广义上的自动化的解决方案, Core Data Programming Guide: What Is Core Data? (apple.com)
- AppleScirpt。 脚本的语言确保了,对于支持脚本的apps和很多macOS的部分的直接控制。Cacos的脚本支持利用了键值对编码去获取和设置支持脚本的对象。
NSScriptKeyValueCoding
的非正式协议中的方法提供了额外的处理键值对编码的能力, 包括通过在multi-value keys的index去获取和设置键值, 强制转化key-value为合适的类型, Introduction to AppleScript Overview
二、Key-Value Coding 基础
2.1 访问对象属性
一个对象通常会在其interface声明属性, 这些属性有以下几种类别:
- Attribute(属性). 简单的值类型。标量、字符串和布尔值。值对象如NSNumber和其他不可变类型如NSColor也被归类为Attributes
- To-one relationships(一一对应关系) 可变对象及它们自身的属性。一个对象的属性能够在对象自身不变的情况下发生变化。例如BankAccount有一个Person的实例对象作为其属性owner, Person实例又有一个address的属性, Person实例在被BankAccount引用的情况下也能修改自己的address, bankaccount的owner没有变化,但是owner发生了变化
- To-many relationships(一对多关系) 这些是集合对象, 你通常使用NSArray或者NSSet的实例去保存一个集合, 常用的集合类也是可用的
上述BankAccount可以表示如下
@interface BankAccount: NSObject
@property (nonatomic) NSNumber* currentBalance; // Attibute
@property (nonatomic) Person* owner; // To-one relation
@property (nonatomic) NSArray<Transaction* >* transactions; // To-many relation
@end
为了确保封装, 一个对象通常在其接口部分提供了访问方法。对象的作者可能显示地写了这些方法, 也可能依赖于编译器自动合成。无论哪种方法, 使用了这些访问方法的作者必须在编译前将属性的名字写入代码。访问器方法的名字变成了使用它的代码的静态的一个部分。比如2.1中的银行账户, 编译器自动合成了你可以通过实例调用的setter
[myAccount setCurrentBalance:@(100.0)];
这很直接, 但是缺乏灵活性。符合键值对编码规范的对象, 另一方面, 提供了更加通用的机制去访问一个对象的属性, 通过字符串标识。
2.1.1 通过key或者keyPath识别一个对象的属性
一个key是一个标识特定属性的string。通常, 按照惯例, 代表了一个属性的key是属性本身的名字, 或者出现在代码里。key必须使用ASCII编码, 不能包含空格, 小写字母打头(也有意外, 比如URL也出现在很多class里面)
因为2.1中的BankAccount是符合编码规范, 能够识别key owner、currentBalance和transaction, 这些都是属性的名字。代替使用setCurrentBalance:方法, 可以通过以下方式设置键值对
[myAccount setValue:@(100.0) forKey:@"currentBalance"];
实际上你可以用同样的方法设置myAccount的所有属性, 改变key参数就可以。因为参数是string, 它可以是在运行时被操纵的变量。
一个key path是一个用于确定对象属性的遍历序列, 是用点分隔的key的string。序列中的第一个key是和接收者相关, 每个子序列key是靠它之前的那个属性进行评估。key path对于一次方法调用获取对象树形结构链下的一个属性来说非常有用。
比如key path, owner.address.street
用于bank account的实例上, 指的就是街道字符串, 位于bank accout下的 owner的 address内, 假定Person和Address两个类都是符合KVC规范的
2.1.2 使用key获取属性值
一个对象在遵循了NSKeyValuecoding
协议的的情况下是符合KVC规范的。 NSObject提供了默认的这个协议重要方法的实现, 一个对象如果继承自NSObject, 自动遵循这个协议。这个对象至少实现了以下基本的基于key的getter:
-
valueForKey:
通过key参数返回属性的值。如果根据访问器匹配模式不能找到key命名的属性, 对象会对自身发送valueForUndefinedKey:
消息。默认的valueForUndefinedKey:
抛出异常NSUndefinedKeyExpection
, 但是子类可能重写了这个方法, 采用了更加优雅的处理方式 -
valueForKeyPath:
返回相对于接收者的key path下的值。如果在这个path序列下的任何一个对象不符合KVC规范, 也就是说找不到默认的valueForKey:
的 不能找到一个访问器方法, 这个对象会收到valueForUndefinedKey:
消息 -
dictionaryWithValuesForKeys:
输入一个数组的key, 返回结果集合。这个方法对于数组中的key对应的每个对象发送valueForKey
消息。返回的是一个字典, 返回的字典keyset是输入的数组- 集合对象, 比如NSArray, NSSet和NSDictionary, 值不能是nil。
- 作为代替, 你可以使用NSNULL对象代表nil值。
- NSNull提供简单的实例代替对象属性的nil值。
- 默认实现
dictionaryWithValuesForKey:
和setValueForKeysWithDictionary:
会自动转换nil和NSNull, 字典中存的是NSNull, 对象property存的是nil
当你使用key path去定位一个propety的时候, 除了key path中最后一个key, 如果其中任一key是一对多类型(指向一个集合类型), 返回值会是一个集合, 集合包括了所有的这个key path的剩余部分的指向的属性对象。比如通过transactions.payee
返回一个数组, 数组包含了每个transaction的payee。多重数组同样奏效。比如通过accounts.transactions.payee
返回一个数组, 数组里面是所有accounts数组中 所有transactions 的payee。
2.1.3 使用key设置属性值
和getter一样, 符合KVC规范的对象提供了一组默认的广义上的setter, 建立在NSObject遵循的NSKeyValueCoding
协议的方法实现上。
-
setValue:forKey:
对应key的对象的属性接收到这个消息, 修改为指定的value。默认的实现setValue:forKey:
自动unwrap NSNumber类型和NSValue类型的代表标量的对象, 赋值给属性。如果key对应的对象属性的setter没有找到, 对象发送自身setValue:forUndefinedKey:
消息。默认这个方法实现抛出异常NSUndefinedKeyException
。子类可能重写了这个方法自定义处理方式。 -
setValue:forKeyPath:
根据key path找到相对于接受者的对应属性, 设置指定值。任何位于key path序列上上但是不符合KVC规范的对象会收到setValue:forUndefinedKey:
消息 -
setValuesForKeysWithDictionary:
输入一个字典, 字典的key对应一个key, 每个值为需要修改的值。默认的实现对每一对键值对调用setValue:forKey:
方法, 有需要的话nil替换为NSNull- 在默认的实现中, 当你企图将非对象属性赋值为nil的时候, 符合KVC规范的对象发送给自身
setNilValueForKey:
消息。这个方法默认的实现抛出异常NSInvalidArgumentException
, 但是一个对象可能重写了这个方法, 给设置了一个默认值或者标记值
- 在默认的实现中, 当你企图将非对象属性赋值为nil的时候, 符合KVC规范的对象发送给自身
2.1.4 使用key简化对象访问
通过以下的例子理解基于key的访问方式如何简化我们的代码。在macOS当中, NSTableView和NSOutlineView对象将一个标识字符串和每一个column相互关联。如果支撑table的model对象不是符合键值对规范的话, table的data source方法会依次强制检查每一个column的标识符找到正确的属性去返回。将来如果又给模型添加了新的属性, 你需要重新给你的data source方法补充对应的返回值
没有KVC的情况下data source的实现
- (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;
}
如果使用KVC, 就算新增属性也不要改变, 因为访问的方式都是一样的
- (id)tableView:(NSTableView *)tableview objectValueForTableColumn:(id)column row:(NSInteger)row
{
return [[self.people objectAtIndex:row] valueForKey:[column identifier]];
}
2.2 访问集合对象
符合KVC规范的对象暴露自身一对多属性的方式和暴露其他属性方式一样。 getter和setter都是valueForKey:
和setvalue:forKey:
(或者key path的等价), 然而, 当你想要操作这些集合对象的时候, 最有效的方式是使用协议提供的可变代理方法。
协议为集合对象访问定义了3种不同的代理方法, 每种包括了key和key path的变体:
-
mutableArrayValueForKey:
andmuatableArrayValueForKeyPath:
返回NSMutableArray的代理对象 -
mutableSetValueForKey:
andmutableSetValueForKeyPath:
返回NSMutableSet代理对象 -
mutableOrderSetValueForKey:
andmutableOrderedSetValueForKeyPath:
返回NSmutbaleOrderedSet对象当你操作这些代理对象的时候, 添加、移除、代替里面的对象, 默认的协议的实现会因此修改潜在的属性。比起使用用
valueForKey:
获取不可变集合对象、用可选内容创建一个新的对象、然后setValue:forKey:
, 这是效率更高的方式。很多情况下, 它也比直接使用可变属性效率更高。这些方法为持有集合对象的对象提供了维系key-value observing规范的额外的便利
2.3 使用集合操作符
2.3.1 示例数据
2.3.2 聚合运算符
- @avg
- @count
- @max
- @min
- @sum
2.3.3 数组运算符
- @distinctUnionOfObjects
- @unionOfObjects
2.3.4 嵌套运算符
- @distinctUnionOfArrays
- @unionOfArrays
- @distinctUnionOfSets
2.4 表示非对象的值
默认的由NSObject提供的KVC协议方法的实现对于对象和非对象都是有效的。默认的实现自动转换对象参数和返回值。这让基于key的getter和setter的signatures保持一致, 即便存储在属性的是一个标量或结构体。
当你调用协议中的gette比如valueForKey:
, 默认实现决定了对应特定key补充value的访问方法或者实例变量, 根据访问器查找模式里面的规则描述。如果返回值不是一个对象, getter会使用这个值去初始化一个NSNumber对象(对于标量), 或者NSValue对象(对于结构体), 然后返回对象。
相似的, 给定一个key, 默认setter,比如setValue:forKey:
决定了属性访问器或实例变量需要的数据类型。如果数据类型不是一个对象, setter首先会发送合适的<type>Value
消息传入值对象, 提炼潜在的数据, 存储这个对象。
- 当你调用协议的setter传入nil给一个非对象属性的时候, setter没有显示的一般的行动方案。因此, 它会发送一个
setNilValueForKey:
消息给接收这个setter调用的对象。默认这个方法抛出异常NSInvalidargumentException
, 但是子类可能重写了这个方法, 比如设置了标记值, 或者提供了一个有意义的值。
2.4.1 Wrapping 和 Unwrapping 标量类型
标量类型默认KVC实现中封装为NSNumber实例。Creation method展示的是getter方法返回时对于标量的封装,初始化为NSNumber。Accessor method 展示的是在setter方式中提炼setter input参数的方法。
Data type | Creation Method | Accessor method |
---|---|---|
BOOL | numberWithBool: | boolValue(iOS) charValue(macOs)* |
char | numberWtihChar: | charValue |
double | numberWithDouble: | doubleValue |
float | numberWithFloat: | floatValue |
int | numberWithInt: | intValue |
long | numberWithLong: | longValue |
long long | numberWithLongLong: | longLongValue |
short | numberWithShort: | shortValue |
unsigned char | numberWtihUnsignedChar: | unsignedChar |
unsigned int | numberWithUnsignedInt: | unsignedInt |
unsigned long | numberWithUnsignedLong: | unsignedLong |
unsigned long long | numberWithUnsignedLongLong: | unsignedLongLong |
unsigned short | numberWithUnsignedShort: | unsignedShort |
2.4.2 Wrapping 和 Unwrapping 结构体类型
Creation method展示的是getter方法返回时,对于结构体的封装,初始化为结构体对象。Accessor method 展示的是在setter方式中提炼setter input参数的方法, 结构体对象转化为value。
Data type | Creation Method | Accessor method |
---|---|---|
NSPoint | valueWithPoint: | pointValue |
NSRange | valueWithRange: | rangeValue |
NSRect | valueWithRect:(macOS only) | rectValue |
NSSize | valueWithSize: | sizeValue |
自动wrapping和unwrapping不仅限于上面四种。结构体类型可以被wrap进NSValue对象。
typedef struct {
float x, y, z;
} ThreeFloats;
@interface MyClass
@property (nonatomic) ThreeFloats threeFloats;
@end
//KVC方法访问结构体
NSValue* result = [myClass valueForKey:@"threeFloats"];
//结构体通过KVC赋值
ThreeFloats floats = {1., 2., 3.};
NSValue* value = [NSValue valueWithBytes:&floats objCType:@encode(ThreeFloats)];
[myClass setValue:value forKey:@"threeFloats"];
2.5 验证属性
KVC协议定义了方法支持属性验证。当你使用基于key的访问器去读写一个符合KVC规范的对象的属性的时候, 你可以通过key或者key path进行验证。当你使用validateValue:forKey:error:
或者validateValue:forKeyPath:error:
方法的时候, 默认的协议实现会查找对象接受验证消息, 验证方法名为validate<Key>:error:
。如果对象没有对应的方法, 默认使用继承的方法, 默认的实现会返回YES。 如果一个属性特定的验证方法存在, 默认实现返回该方法的调用结果。
因为特定属性验证方法通过引用的方式接收值和错误参数, 验证有三种可能结果
-
- 验证方法认为值对象有效, 返回YES, 不修改值或者错误
-
- 验证方法认为值对象无效, 但是选择不修改。这种情况下返回NO, 设置error的引用为一个NSError对象, 这个对象表明了失败的原因
-
- 验证方法认为值对象无效, 但是创建了一个新的有效值取代原来的值。这种情况下方法返回YES, error原封不动。返回前方法将值引用修改为指向新值对象。作出修改的时候, 方法通常是创建一个新的对象, 而不是修改旧的对象, 即便对象是可变的。
Person* person = [[Person alloc] init];
NSError* error;
NSString* name = @"John";
if (![person validateValue:&name forKey:@"name" error:&error]) {
NSLog(@"%@",error);
}
2.5.1 自动验证
通常来说, 既不是KVC协议也不是默认实现规定了任何自动执行验证的机制。你可以在你认为合适的的时候给你的app添加自动验证方法。
某些Cocoa技术在某些情况下自动执行验证。比如Core Data自动执行验证, Core Data Programming Guide: What Is Core Data? (apple.com) 当操作的对象上下文保存的时候。在macOS中, Cocoa bindings也允许置顶验证自动发生 Introduction to Cocoa Bindings Programming Topics (apple.com)
2.6 访问器查找模式
NSObject提供的KVC协议的默认实现, 将基于key的访问器和对象潜在的属性,通过使用一个清晰的规则集合进行匹配。协议的方法使用key参数查找自己对象实例当中的遵循命名惯例的访问器, 实例变量, 相关的方法。虽然你可能不需要修改默认的查询方式, 但它能帮助你理解它是如何运作的, 包括追踪过程和对象规范。
2.6.1 基本getter的查找模式
默认的valueForKey:
给定一个key参数作为输入, 执行以下程序, 在实例接收到这个方法的消息的时候
-
- 查找实例的第一访问器方法,
get<Key>
,<key>
,is<Key>
或者是_<key>
。 在这个顺序下, 如果找到了直接跳到5, 不然进行以下步骤
- 查找实例的第一访问器方法,
-
- 如果没有简单的访问器方法, 查找实例方法中匹配
countOf<Key>
objectIn<Key>AtIndex:
(NSArray定义的原始方法) 和<key>AtIndexes:
(对应NSArray方法objectsAtIndexes:
), 如果找到了第一个方法和剩余方法中的任意一个, 创建一个集合代理对象, 该对象响应所有NSArray方法, 然后返回。不然的话跳到步骤3。
- 代理对象将收到的NSArray消息转化为
countOf<Key>
objectIn<Key>AtIndex:
<key>AtIndexes:
的结合, 发送给创建它的符合KVC规范的对象。 - 如果初始对象也实现了名字为
get<Key>:range:
这样的可选方法, 当合适的时候代理对象也会使用 - 以上情况下, 允许这个潜在的属性为NSArray或者不是NSArray, 但表现的像NSArray
- 如果没有简单的访问器方法, 查找实例方法中匹配
-
- 如果没有简单的访问器或者一组数组访问方法, 查看三个方法
countOf<Key>
enumeratorOf<Key>
memberOf<Key>
(对应NSSet定义的原始方法), 如果三个方法都存在,创建一个集合代理对象, 代理对象能够响应所有NSSets的方法, 然后返回, 不然跳到4。
- 这个对象将收到的所有NSSet的消息转化为
countOf<Key>
enumeratorOf<Key>
memberOf<Key>
的结合发送给创建它的对象。 - 以上情况下, 允许这个潜在的属性为NSSet或者不是NSSet, 但表现得像NSSet
- 如果没有简单的访问器或者一组数组访问方法, 查看三个方法
-
- 如果没有简单的访问方法和一组集合访问方法, 如果接收消息的类的方法
accessIsntancevariablesDirectly
返回YES, 查找实例变量_<key>
_is<Key>
<key>
或者is<Key>
, 这个顺序下, 如果找到了, 直接获取实例变量的值, 跳到5。否则跳到6
- 如果没有简单的访问方法和一组集合访问方法, 如果接收消息的类的方法
-
- 如果这个属性是一个对象指针, 直接返回
- 如果这个对象是一个支持NSNumebr标量类型, 封装成NSNumebr实例返回
- 如果这个对象是一个不支持NSNumber的标量类型, 封装成NSValue对象返回
-
- 都失败了, 调用
valueForUndefinedKey:
, 默认实现下跑出异常, NSObject的子类可能对特殊key有不同的处理方式
- 都失败了, 调用
2.6.2 基本setter的查找方式
默认setValue:forKey:
, 给key和value作为输入, 想要将key属性设置为value(非对象属性设置为value的unwrap, 具体查看2.4)。对象接收到这个消息之后, 使用如下步骤:
-
- 查看第一访问器
set<Key>:
_set<Key>
,这个顺序如果找到了, 响应然后完成
- 查看第一访问器
-
- 没有找到简单的访问器,
accessInstanceVariableDirectly
返回YES, 查看实例变量_<key>
_is<Key>
<key>
is<Key>
。这个顺序下,找到了, 响应然后完成
- 没有找到简单的访问器,
-
- 没有找到, 调用
setValue:forUndefinedKey:
, 默认抛出异常, NSObject的子类kennel提供了特定key的处理方式
- 没有找到, 调用
2.6.3 可变数组的查找模式
2.6.4 可变有序set查找模式
2.6.5 可变set查找模式
三、采用KVC
3.1 实现基本的键值对编码规范
当对一个对象采用键值对编码的时候, 你依赖于NSKeyValueCoding
协议的默认实现, 需要让你的对象是继承于NSObject或者是它的诸多子类之一。默认的实现反过来也依赖于你的实例变量或者ivars和访问器方法遵循特定的格式, 这样它才能在收到KVC消息时将key和属性联系起来, 比如valueForKey:
和setValueForKey:
在OC中坚持使用标准格式, 使用@property
语句申明属性, 运行编译器自动合成ivar和accessor。编译器默认遵循标准格式。
在OC如果你确实需要手动实现访问器或者ivar, 遵循这一节的指导去实现基本标准。为了提供,增强你的对象的集合类型属性的交互,附加功能, 你可以去实现3.2中的方法。为了加强KV的验证, 实现3.3中的方法。
- 默认KVC实现比起这里描述的来说, 作用于更广范围的ivar和访问器。如果你有历史遗留代码使用其他变量和访问器标准, 按照2.6检验默认实现是否能够定位到对象的属性。
3.1.1 基本getter
getter实现返回属性值, 传播过程中可能做自定义的操作, 使用和属性同名的方法
- (NSString*)title
{
// Extra getter logic…
return _title;
}
对于Boolean的属性, 可以加上前缀is
- (BOOL)isHidden
{
// Extra getter logic…
return _hidden;
}
当你的属性是标量或者结构体的时候, 默认KVC实现为了使用协议方法的结构会将值封装在对象里, 具体看2.4。你不需要做额外操作
3.1.2 基本setter
实现setter更改属性值的时候, 方法名为set前缀加上大些第一个字母的属性名
- (void)setHidden:(BOOL)hidden
{
// Extra setter logic…
_hidden = hidden;
}
- 不要在set方法内部调用2.5中的验证方法
当一个属性是非对象类型的时候,协议默认实现会检测其潜在的数据类型, 在setter应用之前,将setValue:forKey:
的对象unwrap为值类型, 具体看2.4。你不需要在setter当中处理。
当nil值将要被写入非对象属性的时候, 你可以重写setNilValueForKey:
去处理,具体看3.3。比如对于boolean类型hide这个方法会将nil变为NO;
- (void)setNilValueForKey:(NSString *)key
{
if ([key isEqualToString:@"hidden"]) {
[self setValue:@(NO) forKey:@”hidden”];
} else {
[super setNilValueForKey:key];
}
}
你可以重写以上方法, 即便是已经允许编译器自动合成setter
3.1.3 实例变量
当其中一个KVC访问器默认实现不能找到一个属性的访问器时, 它会查询类的accessInstanceVariablesDirectly
方法, 看看是否允许直接使用实例变量。默认这个方法会返回YES, 你也可以重写返回NO。
如果你允许使用ivar, 确保它们使用常规命名方式, 属性名加上_前缀。通常编译器会自动合成属性的ivar, 如果你使用了@synthesize
, 你可以自己命名
@synthesize title = _title;
在一些情况下, 可以使用@dynamic
代替使用@synthesize
指示或者允许编译器自动对属性进行合成, 表示通知编译器你将会在runtime提供getter和setter。你可能会采用这种方式避免自动合成getter, 这样你就可以提供集合访问器作为代替, 具体查看3.2。 这种情况下, 你将ivar申明为接口的一部分
@interface MyObject : NSObject {
NSString* _title;
}
@property (nonatomic) NSString* title;
@end
3.2 定义集合方法
3.2.1 访问有序集合
3.2.2 访问无序集合
3.3 处理非对象值
3.4 添加验证
3.4.1 实现验证方法
3.4.2 验证标量值
3.5 描述属性关系
3.6 性能设计
KVC很有效率, 尤其是你依赖于默认的实现处理大部分操作的时候, 但是它确实添加了比直接方法访问更慢一些的一个间接层。只有当你能够从KVC的灵活度受益, 或者你的对象需要借助基于KVC技术的其他Cocoa技术的时候, 你才考虑使用KVC
3.6.1 重写KVC方法
通常, 你的对象时继承自NSObject来确保它符合KVC规范, 然后提供了特定的属性访问器和这篇文档描述的相关方法。你几乎不需要重写默认的KVC访问器的实现, 比如vlaueForKey:
setValue:forKey:
validateValue:ForKey:
, 因为这些实现cache了关于runtime环境的信息来提高效率。如果你重写个这些方法引入自定义的逻辑, 确保你在返回之前调用了superclass的该方法。
3.6.2 优化对多关系
当你实现对多关系时, 有序的访问器组成 在很多方面提供了重要的功能。尤其是可变集合, 具体查看2.2和3.2
3.7 规范清单
3.7.1 Attribue和To-One Relationship 规范
- 实现
<key>
或者is<Key>
方法, 或者创建实例变量<key>
或者_<key>
。编译器会自动完成这些工作当你的属性自动合成时- 虽然属性名通常为小写, 但在协议的默认实现当中大写开头的也是有效的, 比如URL
- 如果属性可变, 实现
set<Key>:
方法。编译器自动完成这个工作, 当你允许自动合成属性的时候- 如果重写了默认的setter, 不要在里面调用任何协议规定的验证方法
- 如果属性是标量, 重写
setNilValueForKey:
方法, 优雅处理 nil值被赋给标量类型的情况
3.7.2 有序的To-Many Relationship 规范
3.7.3 无序的To-Many Relationship 规范
3.7.4 验证
如果需要可以选择对属性提供验证方法
- 实现
validate<Key>:error:
方法, 返回boolean值表示验证结果, 以及error的引用