官方链接.
About Key-Value Coding
KVC 是一种由对象采用 NSKeyValueCoding
协议提供 对其属性的间接访问接口 的机制。当对象遵循 KVC 时,可通过字符串参数通过简洁,统一的消息传递接口找到其属性. 这种间接访问机制补充了 实例变量及其相关访问器方法 的直接访问(即扩充了 实例变量和 setter/getter
的访问方式).
通常使用访问器方法来访问对象的属性。get访问器(或getter
)返回属性的值。set访问器(或setter
)设置属性的值。在 Objective-C 中,您还可以直接访问属性的基础实例变量。以任何这些方式访问对象属性都很简单,但需要调用特定于属性的方法或变量名。随着属性列表的增长或变化,访问这些属性的代码也必须如此。相反,符合键值编码的对象提供了一个在其所有属性中都一致的简单的消息传递接口.
KVC 是一个基本概念, 是许多其他 Cocoa 技术的基础,例如 KVO
,Cocoa bindings
,Core Data
和 AppleScript
能力。在某些情况下,KVC 还有助于简化代码。
Using Key-Value Coding Compliant Objects
对象通常在(直接或间接)继承 NSObject 时采用 KVC,它们都采用 NSKeyValueCoding
协议并为基本方法提供默认实现。这样的对象使其他对象能够通过达成协议的消息传递接口执行以下操作:
访问对象属性。协议指定方法,例如
valueForKey:
和setValue:forKey:
,为了通过名称 或 key 访问对象属性,参数化为字符串。这些默认实现和相关方法使用 key 来定位交互基础数据,如 Accessing Object Properties 中所述。操纵集合属性。访问方法的默认实现使用对象的集合属性(如 NSArray),就像其他属性一样。此外,如果对象定义属性的 集合访问方法,则它允许对集合内容进行 KVC 访问。这通常比直接访问更有效,并允许您通过标准化接口使用自定义集合对象,如 Accessing Collection Properties 所述。
在集合对象上调用
Collection operators
。在符合 KVC 的对象中访问集合属性时,可以将Collection operators
插入到 key字符串 中,如 Using Collection Operators 所述。Collection operators
使用默认的NSKeyValueCoding
getter实现对集合执行操作,然后返回集合的新的过滤版本或表示集合的某些特征的单个值。访问非对象属性。协议的默认实现检测非对象属性,包括标量和结构体,并自动将它们包装和解包为协议接口上使用的对象,如 Representing Non-Object Values 所述。此外,该协议声明了一种方法,该方法允许对象在通过 KVC 在非对象属性上设置 nil值的情况下提供合适的动作。
通过 key-path 访问属性。当对象符合多层次 KVC 时,可以使用基于
key-path
的方法调用,使用单个调用深入到层次结构中获取或设置值。
Adopting Key-Value Coding for an Object (自定义对象使用 KVC)
自定义对象要符合 KVC 要求,需要确保它们采用 NSKeyValueCoding
协议并实现相应的方法,例如作为 valueForKey:
通用getter 和 setValue:forKey:
通用setter。NSObject 遵循了此协议并为这些和其他基本方法提供默认实现。
为了使默认方法工作,需要确保对象的访问器方法和实例变量遵循某些明确定义的模式以允许默认实现以响应 KVC 消息的方式查找对象的属性。然后,为了处理某些特殊情况可以选择通过重写方法来扩展和自定义 KVC。
Key-Value Coding with Swift
Swift 对象默认从 NSObject 或其子类继承是符合 KVC 的。而在 Objective-C 中,属性的访问器和实例变量必须遵循某些模式,Swift 中的标准属性声明会自动保证这一点。另一方面,协议的许多功能要么不相关,要么 Swift 会更好地处理。例如,因为所有 Swift 属性都是对象,所以您永远不会使用默认实现以 对非对象属性的特殊处理。
虽然键值编码协议方法直接转换为 Swift, 但本指南主要关注 Objective-C.
有关将 Swift 与 Cocoa 技术一起使用的更多信息,请阅读使用 Swift 与 Cocoa和 Objective-C(Swift 3)。有关Swift的完整描述,请阅读 Swift 编程语言(Swift 3).
Other Cocoa Technologies Rely on Key-Value Coding (其他基于 KVC 技术)
KVO
. 此机制使对象能够注册 由另一个对象属性的改动驱动的 异步通知,如 Key-Value Observing Programming Guide 所述。Cocoa bindings
. 这一系列技术完全实现了 MVC, 其中 Model 封装应用程序数据,View 显示和编辑数据,Controller 在两者之间进行调解。阅读 Cocoa Bindings Programming Topics 以了解有关Cocoa bindings
的更多信息。Core Data
. 该框架为与对象生命周期和对象图管理相关的常见任务(包括持久性)提供通用和自动化的解决方案。您可以在 Core Data Programming Guide 中了解 Core Data.AppleScript
. 这种脚本语言可以直接控制脚本化应用程序和 macOS 的许多部分。Cocoa 的脚本支持 利用 KVC 来获取和设置 脚本化对象中的信息。NSScriptKeyValueCoding
协议中的方法提供了使用 KVC 的附加功能,包括通过多值键中的索引获取和设置键值,以及将键值强制(或转换)为适当的数据类型。AppleScript Overview 提供了AppleScript 及其相关技术的高级概述。
Accessing Object Properties
对象通常在其接口声明中指定属性,属于以下几种类别之一:
-
Attributes
. 属性变量, 例如标量,字符串或布尔值。值对象诸如 NSNumber 和其他不可变类型 NSColor 也被视为属性。 -
To-one relationships
. 实例变量, 拥有属性的可变对象, 对象的属性可以更改,而对象本身不会更改. -
To-many relationships
. 集合对象, 通常使用 NSArray 或 NSSet的实例, 也可以是自定义集合类.
BankAccount 对象的属性演示了每种类型的属性:
@interface BankAccount : NSObject
@property (nonatomic) NSNumber* currentBalance; // An attribute
@property (nonatomic) Person* owner; // A to-one relation
// A to-many relation
@property (nonatomic) NSArray< Transaction* >* transactions;
@end
为了保持封装,对象通常为其接口上的属性提供访问器方法。对象的作者可以显式地编写这些方法,也可以依赖编译器自动合成它们。无论哪种方式,代码的作者使用这些访问器之一前必须在编译之前将属性名称写入代码中。例如, 编译器会为 BankAccount 对象合成一个实例调用的 setter:
[myAccount setCurrentBalance:@(100.0)];
这是直接的,但缺乏灵活性。另一方面,符合 KVC 的对象提供了使用字符串标识符访问对象属性的更通用的机制。
Identifying an Object’s Properties with Keys and Key Paths
使用键和键路径标识对象的属性
key 是一个字符串标记指定属性. 通常情况下, 按照惯例, 表示属性的键是代码中显示的属性本身的名称, keys 必须使用 ASCII 编码, 不能包含空格, 并且通常以小写字母开头(也有例外, 比如 URL).
BankAccount 类符合 KVC, 所以它承认它的属性名 owner
, currentBalance
, transactions
作为key, 替代调用 setCurrentBalance:
方法:
[myAccount setValue:@(100.0) forKey:@"currentBalance"];
因为参数是字符串, 所以可以是在运行时操作的变量.
key-path
是用点分隔 keys的字符串来指定对象的属性的序列, 序列中第一个键的属性是相对于接收者的,并且每个后续键相对于前一个属性的值进行评估, key-path
对于使用单个方法调用向下钻取到对象层次结构非常有用。
例如,应用于银行帐户实例的 owner.address.street
指存储在银行帐户所有者地址中的街道字符串的值,假设 Person 和 Address 类也符合 KVC。
Getting Attribute Values Using Keys
对象在遵循 NSKeyValueCoding
协议时符合 kVC。继承自 NSObject(提供协议的基本方法的默认实现)的对象会自动遵循协议的某些默认行为。这样的对象至少实现了以下基本 key-based getter:
-
valueForKey:
- 返回由 key 参数指定的属性的值。如果根据 Accessor Search Patterns 中描述的规则无法找到由 key 命名的属性,则该对象会向自身发送valueForUndefinedKey:
消息。默认实现valueForUndefinedKey:
引发了一个NSUndefinedKeyException
,但是子类可能会覆盖此行为并更优雅地处理这种情况。 -
valueForKeyPath:
- 返回相对于 receiver 的指定 key-path 的值, key path sequence 中指定 key 的对象不符合 KVC, 意味着valueForKey:
找不到访问方法, 则会接收到valueForUndefinedKey:
消息. -
dictionaryWithValuesForKeys:
返回 receiver 相关的 keys 的对应的一组值. 方法为每个数组中的 key 调用valueForKey:
. 返回的字典包含所有 key 对应的值.
注意
集合对象,如 NSArray,NSSet 和 NSDictionary,不能包含 nil 的值。相反,使用nil应该 NSNull 对象表示值。NSNull 提供表示 nil对象属性值的单个实例.dictionaryWithValuesForKeys:
和setValuesForKeysWithDictionary:
默认实现会在 NSNull(在字典参数中)和 nil(在存储属性中)之间自动进行转换。
当使用 key-path 查找属性时, 如果 key-path 中 (除了最后一个) key 是 to-many relationship (意味着引用一个集合类型), 返回值是这个 to-many key 右边的 key 对应的值的集合, 例如, key path: transactions.payee
会返回一个包含所有 transactions 对应的 payee 的数组(即, 查找所有 transactions 下面的 payee 并返回), key path: accounts.transactions.payee
会返回所有 accounts 下所有 transactions 对应的 payee 数组.
Setting Attribute Values Using Keys
与 getter
一样,符合 KVC 的对象也提供了一小组通用 setter
,NSObject 默认行为基于以下 NSKeyValueCoding
协议的实现:
setValue:forKey:
- 将给定的值设置为相对 receiver 指定 key 的值。setValue:forKey:
默认实现自动解包表示标量和结构的 NSNumber 和 NSValue 对象,并将它们 assign 给属性。有关包装和解包语义的详细信息,请参阅 Representing Non-Object Values。
如果没有实现指定的 key 的 setter,则该对象会向自身发送setValue:forUndefinedKey:
消息。默认实现setValue:forUndefinedKey:
引发了NSUndefinedKeyException
。但是,子类可以重写此方法以自定义方式处理请求。setValue:forKeyPath:
- 设置相对于 receiver 的指定 key-path 的给定值。key-path 序列中不符合 特定 key 的 KVC 的任何对象 都会收到setValue:forUndefinedKey:
消息。setValuesForKeysWithDictionary:
- 使用字典的 keys 标识属性,使用指定字典中的值设置 receiver 的属性。默认实现为每个键-值对调用setValue:forKey:
,如果需要的话用 NSNULL 对象代替 nil.
当想要为非对象属性设置 nil值时, 符合 KVC 的对象回给自己发送 setNilValueForKey:
消息. setNilValueForKey:
的默认实现会抛出一个 NSInvalidArgumentException
, 但是对象可以覆盖此行为以替换默认值或标记值,如处理非对象值中所述。如 Handling Non-Object Values 所述.
Using Keys to Simplify Object Access (使用键简化对象访问)
直接举例, 根据 column 返回对象的对应属性:
- (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;
}
- (id)tableView:(NSTableView *)tableview
objectValueForTableColumn:(id)column
row:(NSInteger)row {
return [[self.people objectAtIndex:row] valueForKey:[column identifier]];
}
Accessing Collection Properties
您可以像 使用任何其他对象使用 valueForKey:
和 setValue:forKey:
(或它们的键路径等价物)一样获取或设置集合对象。但是,当您想要操纵这些集合的内容时,通常最有效的方法是使用协议定义的可变代理方法。
协议为集合类型对象的访问接口定义了三个不同的代理方法每个都有一个 key 和 key-path 变体:
-
mutableArrayValueForKey:
andmutableArrayValueForKeyPath:
返回一个行为类似于NSMutableArray对象的代理对象 -
mutableSetValueForKey:
andmutableSetValueForKeyPath:
返回一个行为类似于NSMutableSet对象的代理对象 -
mutableOrderedSetValueForKey:
andmutableOrderedSetValueForKeyPath:
返回一个行为类似于NSMutableOrderedSet对象的代理对象
对代理对象进行添加 / 删除 / 替换对象等操作时, 协议的默认实现会相应地修改基础属性. 这比以 valueForKey:
获取非可变集合对象, 创建可变已修改的集合对象,然后使用setValue:forKey:
将其存回对象 更有效,
在许多情况下,它也比直接使用可变属性更有效。这些方法提供了 维护集合中对象的 KVO 的额外好处(有关详细信息,请参阅 Key-Value Observing Programming Guide)。
Using Collection Operators (使用集合运算符)
当你向遵循 KVC 的对象发送 valueForKeyPath:
消息时, 你可以在 key-path 中嵌入collection operator
. collection operator
是一个关键词之一, 前面用一个'@'符号指定 getter 应该执行的操作, 以便在返回之前以某种形式操作数据, NSObject 通过这个行为为 valueForKeyPath:
提供默认实现.
当 key-path 包含 collection operator
时, 任何在它之前也就是 left-key-path 指定了基于集合的需要操作的 receiver. 如果给一个集合类型对象发送消息, 比如 NSArray 的实例对象, 则可以省略 left-key-path.
operator
后面的也就是 right-key-path, 指定 operator
应处理的集合中的属性, 除了 '@count', 所有的 collection operators
都需要 right-key-path.
the operator key path format :
Collection operators
列出了 operation 的三种类型:
-
Aggregation Operators
- 聚合操作符, 以某种方式合并集合中的所有对象, 并返回一个通常与 right-key-path 命名的数据类型的对象.'@count' operator
除外, 它没有 right-key-path, 并且总是返回 NSNumber 类型实例. -
Array Operators
- 数组操作符, 返回一个 包含集合中对象 的子集(NSArray 实例)。 -
Nesting Operators
- 嵌套操作符, 操作包含集合的集合, 并根据操作符返回 NSArray 或 NSSet 实例, 它以某种方式组合嵌套集合中的所有对象.
Sample Data 示例:
基于上面的 BankAccount
类, 有一个 Transaction
对象的数组 NSArray< Transaction* >* transactions
:
@interface Transaction : NSObject
@property (nonatomic) NSString* payee; // To whom
@property (nonatomic) NSNumber* amount; // How much
@property (nonatomic) NSDate* date; // When
@end
假设 BankAccount 实例的 transactions 数组有如下数据, 从 BankAccount 对象内部调用示例 :
Aggregation Operators (聚合操作符)
@avg
当使用 '@avg' 操作符时, valueForKeyPath:
通过 right-key-path 读取集合中每个元素读取属性, 并转换为 double 类型(nil 用 0 代替), 计算他们的平均值, 然后返回 NSNumber 包含结果的实例.
例如, 获取 transactions 中所有 transaction amount 的平均值:
NSNumber *transactionAverage =
[self.transactions valueForKeyPath:@"@avg.amount"];
格式化的结果为 $456.54
.
@count
当使用 '@count' 操作符时, valueForKeyPath:
以 NSNumber 实例返回集合中所有对象的数量, 如果有 right-key-path 忽略.
NSNumber *numberOfTransactions =
[self.transactions valueForKeyPath:@"@count"];
numberOfTransactions
结果为 13
.
@max
当使用 '@max' 操作符时, valueForKeyPath:
会通过 right-key-path 在集合中查找并返回最大的. 查找会使用由 Foundation 类(例如 NSNumber) 定义的 compare:
方法.
所以, right-key-path 指定的属性必须是一个对这个消息有 有意义的响应. 查找忽略集合中的 nil 值.
transactions 中 最大 date 值:
NSDate *latestDate = [self.transactions valueForKeyPath:@"@max.date"];
latestDate
值是 Jul 15, 2016
.
@min
原理同 '@max', 返回最小的元素.
NSDate *earliestDate = [self.transactions valueForKeyPath:@"@min.date"];
latestDate
值是 Dec 1, 2015
.
@sum
原理同 '@avg', 返回所有元素的和.
NSNumber *amountSum = [self.transactions valueForKeyPath:@"@sum.amount"];
amountSum
is $5,935.00
.
Array Operators
valueForKeyPath:
返回 与 right-key-path 指定的特定对象集 相对应的数组对象。
使用
Array Operators
时, 如果子对象是 nil,valueForKeyPath:
会抛出异常.
@distinctUnionOfObjects
valueForKeyPath:
返回 right-key-path
指定属性的去重后的数组.
payee 去重后的结果:
NSArray *distinctPayees =
[self.transactions valueForKeyPath:@"@distinctUnionOfObjects.payee"];
distinctPayees 包含为: Car Loan, General Cable, Animal Hospital, Green Power, Mortgage
.
@unionOfObjects
@unionOfObjects
和 @distinctUnionOfObjects
行为相似, 但是不会删除重复对象.
NSArray *payees =
[self.transactions valueForKeyPath:@"@unionOfObjects.payee"];
payees 包含: Green Power, Green Power, Green Power, Car Loan, Car Loan, Car Loan, General Cable, General Cable, General Cable, Mortgage, Mortgage, Mortgage, Animal Hospital
, 注意重复项.
@distinctUnionOfArrays operator
会去除重复项.
Nesting Operators
嵌套集合操作符, 子对象为 nil 会抛出异常.
示例:
NSArray* moreTransactions = @[<# transaction data #>];
NSArray* arrayOfArrays = @[self.transactions, moreTransactions];
用另一个数组 moreTransactions
, 使用下表数据填充:
@distinctUnionOfArrays
遍历 arrayOfArrays
中每个数组中的对象的 payee 进行合并, 并去重返回.
NSArray *collectedDistinctPayees =
[arrayOfArrays valueForKeyPath:@"@distinctUnionOfArrays.payee"];
collectedDistinctPayees
包含: Hobby Shop, Mortgage, Animal Hospital, Second Mortgage, Car Loan, General Cable - Cottage, General Cable, Green Power
.
@unionOfArrays
@unionOfArrays
和 @distinctUnionOfArrays
相似, 但是不会去重.
对 arrayOfArrays
所有数组中对象的 payee 属性进行合并操作:
NSArray *collectedPayees =
[arrayOfArrays valueForKeyPath:@"@unionOfArrays.payee"];
collectedPayees
数组包含: Green Power, Green Power, Green Power, Car Loan, Car Loan, Car Loan, General Cable, General Cable, General Cable, Mortgage, Mortgage, Mortgage, Animal Hospital, General Cable - Cottage, General Cable - Cottage, General Cable - Cottage, Second Mortgage, Second Mortgage, Second Mortgage, Hobby Shop
.
@distinctUnionOfSets
和 @distinctUnionOfArrays
相似, 返回结果为 NSSet 实例.
Representing Non-Object Values (表示非对象值)
NSObject 提供的 KVC 协议方法默认实现会作用于对象和非对象属性上. 默认实现会自动在对象参数, 返回值, 非对象属性之间进行转换. 这意味着 key-based 的 getter 和 setter 的签名(即使在存储属性是标量或结构体时也)保持一致。
当调用协议的 getter, 比如 valueForKey:
, 默认实现根据 Accessor Search Patterns 中描述的规则确定为指定键提供值的特定访问器方法或实例变量. 如果返回值不是对象, getter 会使用这个值初始一个 NSNumber 对象 或 NSValue(对于结构体) 对象替代.
同样地, setter 比如 setValue:forKey:
通过特定键或访问方法或实例变量时确定的数据类型, 如果数据类型不是对象, setter 首先会向传入的值对象发送适当的
给一个非对象属性以 nil 值调用 KVC 协议的 setter 时, setter 没有明确合适的方法.所以它发送
setNilValueForKey:
消息给这个对象用来接收 setter 的调用. 默认实现会抛出异常, 但是子类可以重写, Handling Non-Object Values 中描述了, 比如自定义值, 或提供一个有意义的默认实现.
Wrapping and Unwrapping Scalar Types (包装和展开标量类型)
下表列出了使用 NSNumber 转换的标量类型, 每个数据类型都列出了用基本数据初始化 NSNumber 对象的创建方法作为 getter, 以及设置操作中从 setter 参数中提取值的访问方法:
注意:
在macOS中,由于历史原因,BOOL类型定义为signed char,而KVC不区分这些。因此, 当 key 是 BOOL 类型时, 你不能传一个 string 类型(比如: @"true" 或 @"YES"). KVC 会调用charValue
方法(因为 BOOL 本质上是 char), 但是 NSString 类型没有实现这个方法, 会触发 runtime 报错. 作为替代, 只能传入 NSNumber 类型, 比如 @(1) 或 @(YES) 作为setValue:forKey:
参数. 这一限制并不适用于 iOS, 因为BOOL 被定义为本地布尔类型 bool, KVC 会调用boolValue
方法, 作为 NSNumber 或 可以格式化 NSString 对象.
Wrapping and Unwrapping Structures (包装和展开结构)
默认用于包装和展开创建和访问方法 NSPoint
,NSRange
,NSRect
,和 NSSize
结构的访问器:
自动包装和解包不仅限于 NSPoint
,NSRange
,NSRect
,和 NSSize
. 也可以是 NSValue
对象:
typedef struct {
float x, y, z;
} ThreeFloats;
@interface MyClass
@property (nonatomic) ThreeFloats threeFloats;
@end
通过 KVC 获取 threeFloats
:
NSValue* result = [myClass valueForKey:@"threeFloats"];
valueForKey:
的默认实现调用 threeFloats
的 getter
, 然后转换为 NSValue 对象返回.
相似地, 也可以用 KVC 设置 threeFloats
的值:
ThreeFloats floats = {1., 2., 3.};
NSValue* value =
[NSValue valueWithBytes:&floats objCType:@encode(ThreeFloats)];
[myClass setValue:value forKey:@"threeFloats"];
默认实现使用 getValue:
消息解值,然后使用生成的结构调用 setThreeFloats:
.
Validating Properties (属性验证)
KVC 协议定义了支持属性验证的方法。就像使用 key-based 的访问器来读写符合 KVC
的对象的属性一样,您也可以按 key(或 key-path)验证属性。当您调用validateValue:forKey:error:
(或 validateValue:forKeyPath:error:
)方法时,协议的默认实现会搜索接收验证消息的对象(或末尾 key-path 的对象),以查找名称与 validate
模式匹配的方法。如果对象没有此类方法,则默认情况下验证成功,并返回默认实现YES。当存在特定于属性的验证方法时,默认实现将返回调用该方法的结果。
由于特定于属性的验证方法 通过引用接收的值和错误参数,因此验证有三种可能的结果:
- 验证方法认为参数有效并返回 YES.
- 验证方法认为参数无效, 并不修改, 则返回 NO, 且会将 error 指针指向 NSError 对象指示失败的原因.
- 验证方法认为参数无效, 但是创建一个新的可以有效作为替换的参数, 方法返回 YES, 同时保持错误对象不变。在返回之前,该方法修改值引用以指向新值对象。当它进行修改值时,该方法总是创建一个新对象,而不是修改旧对象,即使值对象是可变的。
调用验证方法:
Person* person = [[Person alloc] init];
NSError* error;
NSString* name = @"John";
if (![person validateValue:&name forKey:@"name" error:&error]) {
NSLog(@"%@",error);
}
Automatic Validation
通常, 不管是 KVC 的协议还是默认的实现都没有实现任何自动执行验证的机制. 相反, 在适合您的 app 时使用验证方法。
某些其他 Cocoa 技术在某些情况下会自动执行验证。例如,Core Data 会在保存 managed object context 时自动执行验证(请参阅 Core Data Programming Guide)。此外,在macOS中,Cocoa bindings 允许您指定自动进行验证(有关详细信息,请参阅Cocoa Bindings Programming Topics)。
Accessor Search Patterns (访问方法的查找)
NSObject 提供的 NSKeyValueCoding
协议方法的默认实现 使用一组明确定义的规则 映射 key-based 访问方法 到 对象的基本属性. 这些协议方法使用一个 key 参数在自己的对象实例中查找访问方法, 实例变量, 还有遵循某些命名约定的相关方法. 尽管您很少修改此默认搜索模式,但对了解它的工作原理可能会有所帮助,比如跟踪 KVC 对象的行为,也可以使您自己的对象兼容.
Search Pattern for the Basic Getter (Getter 的查找)
valueForKey:
默认实现, 给一个 key 参数, 执行以下一个类实例接收 valueForKey:
消息调用的程序:
- 查找实例第一个访问方法, 顺序查找
get
,
,isKey
,_
. 如果找了直接调用, 根据结果到 step5, 否则执行 step2. - 查找实例的方法匹配
countOf
andobjectIn
(对应 NSArray 定义的原始方法) 和AtIndex:
(对应 NSArray 方法AtIndexes: objectsAtIndexes:
).
如果第一个和后面两个的其中一个被找到, 创建一个响应所有 NSArray 方法的集合代理对象并返回, 否则执行 step3.
代理对象会转发收到的上面的消息给创建这个代理对象的对象.
-(也就是说包含有上面方法的对象虽然不是 NSArray 对象, 但是会创建一个代理对象, 用来把它当做 NSArray 处理) - 如果没有找到访问方法或 NSArray 的访问方法组, 就会查找三重方法
countOf
,enumeratorOf
, 和memberOf
(对应于 NSSet 类的原始方法).:
如果三个方法都找到了, 则创建一个响应 NSSet 所有方法的集合代理并返回, 执行 step4.
这个代理对象会转发所有上面找到的消息给创建它的对象.
(同 NSArray 一样, 创建一个代理对象, 作为一个 NSSet处理该对象) - 如果上述方法均未找到, 且接收者对象的
accessInstanceVariablesDirectly
(直接访问实例变量) 方法返回 YES, 则会顺序查找_
,_is
,
,is
. 直接获取实例变量的值执行 step5, 否则执行 step6. - 如果收到的属性值是一个对象指针, 简单返回.
如果是标量类型支持 NSNumner, 用包装 NSNumber 返回, 不支持则包装成 NSValue 对象返回. - 如果所有的失败了, 执行
valueForUndefindKey:
, 默认抛出异常, 子类可以重写.
Search Pattern for the Basic Setter (setter 的查找)
setValue:forKey:
方法的默认实现是传入 key 和 value 参数来设置对象内部属性名为 key 属性的 value(如果是非对象属性, 参见 Representing Non-Object Values
), 调用程序如下:
- 顺序查找访问方法名
set
,_set
. 找到了执行. - 如果没有找到访问方法, 如果类方法
accessInstanceVariablesDirectly
(直接访问属性) 返回 YES, 顺序查找_
,_is
,
,is
实例变量, 如果找到了则直接设置传入的值. - 如果没有找到访问方法或实例变量, 执行
setValue:forUndefinedKey:
. 抛出异常, 子类可重写.
Search Pattern for Mutable Arrays (Mutable Arrays 的查找)
mutableArrayValueForKey:
传入一个 key 参数, 返回一个对象中属性名为 key 的可变数组代理接收访问方法的调用:
查找一组方法名像
insertObject:in
andAtIndex: removeObjectFrom
(对应于 NSMutableArray 原始方法AtIndex: insertObject:atIndex:
andremoveObjectAtIndex:
), 或者insert
and:atIndexes: remove
(对应 NSMutableArray 的AtIndexes: insertObjects:atIndexes:
andremoveObjectsAtIndexes:
方法).
如果对象有至少一个 insertion 和一个 removal 方法, 返回一个响应 NSMutableArray 消息并发送给mutableArrayValueForKey:
方法的原始接收者 的代理对象.
当对象接收到mutableArrayValueForKey:
消息并实现一个可选的 replace 方法像replaceObjectIn
或AtIndex:withObject: replace
, 代理对象那个会在适合的适合利用它们以达到最佳性能.AtIndexes:with : -
如果对象没有 mutable array 的方法, 以 命名如
set
方法替换. 这种情况下, 返回一个通过向: mutableArrayValueForKey:
的原始接收者发送set
消息来响应 NSMutableArray 消息的代理对象。: 此步骤中描述的机制与前一步相比效率要低得多,因为它可能涉及重复创建新的集合对象,而不是修改现有的集合对象。因此,在设计自己的键值编码兼容对象时,通常应该避免使用它。
如果上面的都失败了, 并且接收者
accessInstanceVariablesDirectly
(直接访问实例变量) 方法响应为 YES, 则顺序查找命名如_
的实例变量.
如果找到实例变量, 返回一个代理对象, 该对象将接收到的每个 NSMutableArray 消息转发给实例变量的值,该值通常是 NSMutableArray 的实例或它的子类之一。如果全部失败, 返回一个给
mutableArrayValueForKey:
消息原始接收者发送setValue:forUndefinedKey:
消息的对象. 默认抛出异常, 子类可以重写.
Search Pattern for Mutable Ordered Sets
mutableOrderedSetValueForKey:
和 valueForKey:
相似, 但是前者返回可变集合代理对象, 后者返回不可变集合代理对象:
查找命名如
insertObject:in
andAtIndex: removeObjectFrom
(对应于 NSMutableOrderedSet 定义的原始方法), andAtIndex: insert
and:atIndexes: remove
(对应AtIndexes: insertObjects:atIndexes:
andremoveObjectsAtIndexes:
).
如果至少找到一个 insertion 和一个 removal 方法, 返回发送所有 NSMutableOrderedSet 消息给mutableOrderedSetValueForKey:
消息的接收者 的代理对象.
代理对象也使用形如replaceObjectIn
orAtIndex:withObject: replace
when they exist in the original object.AtIndexes:with : -
如果没有找到方法, 查找访问方法形如
set
, 每当返回的代理对象接收到一个NSMutableOrderedSet
消息时, 都会发送一个set
消息给mutableOrderedSetValueForKey:
的接收者.此步骤中描述的机制与前一步相比效率要低得多,因为它可能涉及重复创建新的集合对象,而不是修改现有的集合对象。因此,在设计自己的键值编码兼容对象时,通常应该避免使用它。
如果上述查找失败, 且接收者的
accessInstanceVariablesDirectly
类方法返回 YES, 顺序查找_
,
实例变量, 返回的代理对象将它接收到的任何 NSMutableOrderedSet 消息转发给实例变量的值,该值通常是 NSMutableOrderedSet 的实例或它的子类之一.如果全部失败, 返回的代理对象发送一个
setValue:forUndefinedKey:
消息给mutableOrderedSetValueForKey:
的接收者, 默认抛出异常, 对象可以重写.
Search Pattern for Mutable Sets
mutableSetValueForKey:
返回一个 内部为接收访问方法调用的对象 的可变代理 set:
- 查找命名形如
add
和Object: remove
(对应于 NSMutableSet 原始方法Object: addObject:
和removeObject:
) 还有add
和: remove
(对应于 NSMutableSet 的: unionSet:
和minusSet:
方法), 如果找到至少一个 addition 和 removal 方法, 返回一个发送 NSMutableSet 消息给mutableSetValueForKey:
的接收者 的代理代理对象.
代理对象为了最佳性能也使用intersect
或: set
, 如果可用的话.: - 如果
mutableSetValueForKey:
调用的接收者是一个 managed object, 不会继续查找. see Managed Object Accessor Methods in Core Data Programming Guide for more. - 如果没有找到方法, 且不是 managed object, 返回对象发送
set
消息给: mutableSetValueForKey:
的接收者. - 如果上述失败, 且
AccessInstanceVariableDirectly
类方法返回 YES, 顺序查找_
,
, 如果找到实例变量, 返回的代理对象将它接收到的任何 NSMutableOrderedSet 消息转发给实例变量的值,该值通常是 NSMutableOrderedSet 的实例或它的子类之一. - 如果全部失败, 返回的代理对象发送一个
setValue:forUndefinedKey:
消息给mutableSetValueForKey:
的接收者作为响应.
Achieving Basic Key-Value Coding Compliance
采用 KVC 时, 通过继承 NSObject 或其子类让对象具有 NSKeyValueCoding
协议的默认实现. 反过来,默认实现依赖于您根据某些明确定义的模式定义对象的实例变量(或ivars)和访问器方法,以便在接收 KVC 消息时,它可以将键字符串与属性相关联,例如 valueForKey:
和 setValue:forKey:
.
在 OC 中通常遵循 KVC 的标准模式是通过使用 @property 简单声明, 允许编译器自动合成 ivar 和访问方法.
如果您确实需要在 OC 中手动实现访问方法或ivars,请遵循本节中的指导原则来维护基本遵从性。要提供用任何语言增强与对象集合属性交互的附加功能,请实现 Defining Collection Methods 中描述的方法。要使用键值验证进一步增强对象,请实现 Adding Validation 描述的方法。
Basic Getters
getter 的实现是返回属性的值, 当需要额外工作时, 使用一个用属性命名方法:
- (NSString*)title {
// Extra getter logic…
return _title;
}
如果是 Boolean, 则可以添加前缀 'is':
- (BOOL)isHidden {
// Extra getter logic…
return _hidden;
}
标量和结构体的默认实现会将值用一个对象包装作为协议方法的接口, 参见 Representing Non-Object Values.
Basic Setters
setter 实现属性值的存储, 用一个首字母大写并包含 'set' 前缀的方法:
- (void)setHidden:(BOOL)hidden {
// Extra setter logic…
_hidden = hidden;
}
注意:
永远不要在set
方法中调用 Validating Properties 中描述的验证方法.:
当属性不是对象类型时,比如 Boolean hidden, 协议默认实现会查询底层数据类型, 并解包 setValue:forKey:
中的对象值在提供给 setter 之前, 参见 Representing Non-Object Values.
如果需要将 nil 值写入非对象类型, 需要重写 setNilValueForKey:
方法, 参见 Handling Non-Object Values. hidden 属性的适当行为可能只是将 nil 解释为 NO:
- (void)setNilValueForKey:(NSString *)key {
if ([key isEqualToString:@"hidden"]) {
[self setValue:@(NO) forKey:@”hidden”];
} else {
[super setNilValueForKey:key];
}
}
如果合适,即使您允许编译器合成setter, 也可以提供上述方法覆盖。
Instance Variables
如果 KVC 的访问方法之一不能找到属性的访问方法, 则它需要类方法 accessInstanceVariablesDirectly
来查看当前类是否允许直接使用实例变量. 默认 YES, 也可以重写为 NO.
如果允许, 确认使用通常方法命名, 在属性名加前缀 '_', 通常编译器自动合成属性时实现, 但是如果使用一个显式的 @synthesize
指令,你可以强制自己命名:
@synthesize title = _title;
在某些情况下,不使用 @synthesize
指令或允许编译器自动合成属性,而是使用 @dynamic
指令通知编译器,您将在运行时提供 getter 和 setter。您可以这样做,以避免自动合成 getter,这样就可以提供集合访问器(setter / getter),如定义集合方法中所述。在这种情况下,您自己声明 ivar 作为接口声明的一部分:
@interface MyObject : NSObject {
NSString* _title;
}
@property (nonatomic) NSString* title;
@end
Defining Collection Methods
当使用标准命名规则创建访问方法和 ivars 时, 参见 Achieving Basic Key-Value Coding Compliance, KVC 的默认实现可以在响应 KVC 消息是定位到它们, 对于表示 to-many 关系的集合对象和其他属性都是如此。如果您实现了集合访问器方法,而不是集合属性的基本访问器,或者除了基本访问器之外,您可以:
- Model to-many relationships with classes other than NSArray or NSSet.
将 to-many 关系以类作 Model, 而不是用 NSArray/NSSet 类. - Achieve increased performance when mutating the contents of a to-many relationship.
在修改 to-many 关系的内容时实现更高的性能。协议的默认实现使用集合方法对基础属性进行适当的修改,而不是使用基本的 setter 对每次更改都要重复创建新的集合对象。 - Provide key-value observing compliant access to the contents of your object’s collection properties.
提供对象的集合属性内容的 KVO.
您可以实现两类集合访问器中的一种,这取决于您希望关系表现为索引的有序集合(如NSArray对象)还是无序的、惟一的集合(如NSSet对象)。无论哪种情况,都要实现至少一组方法来支持对属性的读访问,然后添加另一组方法来启用集合内容的突变。
Accessing Indexed Collections (有序集)
添加索引访问方法提供 counting, retrieving, adding, and replacing objects 的顺序关系机制. 对象经常是 NSArray 或 NSMutableArray, 但是如果提供集合访问方法, 就可以像操作数组一样操作实现这些方法的任意对象属性。(实现集合访问方法, 可以让对象像 array 一样操作其中的对象).
Indexed Collection Getters
集合属性没有默认的 getter, 如果实现以下 indexed collection getter methods, 协议的默认实现, 为了响应 valueForKey:
消息, 返回一个形如 NSArray 的代理对象, 但是调用如下集合方法工作:
-
countOf
返回 to-many 关系中的数量 NSUInteger, 就像是 NSArray 的原始方法count
.
例如: 表示 transactions 返回数组的数量- (NSUInteger)countOfTransactions { return [self.transactions count]; }
-
objectIn
orAtIndex: AtIndexes:
第一个返回 to-many 关系中指定 index 的对象, 第二个返回包含 NSIndexSet 中 indexes 指定的对象的数组. 对应 NSArray 的objectAtIndex:
和objectsAtIndexes:
, 只需要实现一个方法.transactions
的对应方法实现:- (id)objectInTransactionsAtIndex:(NSUInteger)index { return [self.transactions objectAtIndex:index]; } - (NSArray *)transactionsAtIndexes:(NSIndexSet *)indexes { return [self.transactions objectsAtIndexes:indexes]; }
-
get
:range:
可选方法, 但是会影响性能, 返回集合指定 range 的对象集, 对应 NSArray 的getObjects:range:
方法.transactions
实现:- (void)getTransactions:(Transaction * __unsafe_unretained *)buffer range:(NSRange)inRange { [self.transactions getObjects:buffer range:inRange]; }
Indexed Collection Mutators
使用索引访问器支持可变到多的关系需要实现一组不同的方法。当您提供这些setter方法时,作为对mutableArrayValueForKey:
消息的响应,默认实现将返回一个代理对象,其行为类似于 NSMutableArray 对象,但使用对象的方法来完成其工作。这通常比直接返回 NSMutableArray 对象更有效。它还使 to-many 关系的内容符合键值观察(参阅 Key-Value Observing Programming Guide)。
让你的可变有序的 to-many 对象 符合 KVC, 需要实现以下方法:
-
insertObject:in
orAtIndex: insert
:atIndexes:
NSMutableArray 的方法相似insertObject:atIndex:
andinsertObjects:atIndexes:
.
仅需要实现一个:- (void)insertObject:(Transaction *)transaction inTransactionsAtIndex:(NSUInteger)index { [self.transactions insertObject:transaction atIndex:index]; } - (void)insertTransactions:(NSArray *)transactionArray atIndexes:(NSIndexSet *)indexes { [self.transactions insertObjects:transactionArray atIndexes:indexes]; }
-
removeObjectFrom
orAtIndex: remove
AtIndexes: - (void)removeObjectFromTransactionsAtIndex:(NSUInteger)index { [self.transactions removeObjectAtIndex:index]; } - (void)removeTransactionsAtIndexes:(NSIndexSet *)indexes { [self.transactions removeObjectsAtIndexes:indexes]; }
-
replaceObjectIn
AtIndex:withObject: or replace AtIndexes:with : - (void)replaceObjectInTransactionsAtIndex:(NSUInteger)index withObject:(id)anObject { [self.transactions replaceObjectAtIndex:index withObject:anObject]; } - (void)replaceTransactionsAtIndexes:(NSIndexSet *)indexes withTransactions:(NSArray *)transactionArray { [self.transactions replaceObjectsAtIndexes:indexes withObjects:transactionArray]; }
Accessing Unordered Collections (无序集)
添加无序集合访问器方法,以提供访问和修改无序关系中的对象的机制。通常,这个关系是 NSSet 或 NSMutableSet 对象的实例。然而,当您实现这些访问器时,您可以让任何类对关系进行建模,并使用键值编码进行操作,就像它是NSSet 的一个实例一样。
Unordered Collection Getters
当你提供了如下 getter 方法返回集合中对象的数量, 遍历集合对象并测试一个对象是否已经在集合中, 协议方法的默认实现, 为了响应 valueForKey:
消息, 返回一个代理对象形如 NSSet, 但是调用如下方法工作:
- countOf
required method 返回 item 的数量,对应 NSSet 的 count 方法.
- (NSUInteger)countOfEmployees {
return [self.employees count];
}
-
enumeratorOf
required method 返回一个 NSEnumerator 实例用来遍历所有 items. 关于enumerators
查看 Enumeration: Traversing a Collection’s Elements in Collections Programming Topics. 方法对应 NSSet 的objectEnumerator
方法.- (NSEnumerator *)enumeratorOfEmployees { return [self.employees objectEnumerator]; }
-
memerOf
:
该方法比较参数和集合中的对象, 并返回相同的对象, 或 nil 如果没有找到如果手动实现 comarisons, 通常使用isEqual:
进行比较.- (Employee *)memberOfEmployees:(Employee *)anObject { return [self.employees member:anObject]; }
Unordered Collection Mutators
支持可变无序的访问方法需要实现额外的方法. 实现可变无序的访问方法允许你的对象提供一个无序 set 代理对象 作为 mutbleSetValueForKey:
方法, 实现这些访问器比依赖 直接返回可变对象的访问器 对关系中的数据进行更改要有效得多。这可以使你的类收集的对象都符合 KVO.
为了解决可变无序多关系的 KVC 问题,需要实现以下方法:
-
add
orObject: add
:
添加一个或多个 item 到关系中. 当添加多个 items 时, 请确保关系中不存在等效的对象, 这些类似于NSMutableSet 的addObject:
和unionSet:
方法, 只需要其中一种方法:- (void)addEmployeesObject:(Employee *)anObject { [self.employees addObject:anObject]; } - (void)addEmployees:(NSSet *)manyObjects { [self.employees unionSet:manyObjects]; }
-
remove
orObject: remove
:
删除一个或一组 items. 对应于 NSMutableSet 的 removeObject: 和 minusSet: 方法. 只有一个是必须的:- (void)removeEmployeesObject:(Employee *)anObject { [self.employees removeObject:anObject]; } - (void)removeEmployees:(NSSet *)manyObjects { [self.employees minusSet:manyObjects]; }
-
intersect
:
该方法接收 NSSet 类型参数, 删除参数 set 和接收者 set 不相同的对象. 等同于 NSMutableSet 的intersectSet:
方法. 当分析指出在更新集合内容的性能问题时,可以选择实现此方法。- (void)intersectEmployees:(NSSet *)otherObjects { return [self.employees intersectSet:otherObjects]; }
Handling Non-Object Values
通常,与键值编码兼容的对象依赖于键值编码的默认实现来自动包装和解包装非对象属性,如在 Representing Non-Object Values 时所述。不过,您可以覆盖默认行为。这样做最常见的原因是处理在非对象属性上尝试存储 nil 值。
如果符合 KVC 的对象收到一个传入设置非对象值为 nil 的 setValue:forKey:
消息, 默认没有适当的、概括的行动方针. 所以它发送 setNilValueForKey:
消息给它自己, 可以重写. 默认抛出异常.
比如, 以 nil 来设置年龄为 0:
- (void)setNilValueForKey:(NSString *)key {
if ([key isEqualToString:@"age"]) {
[self setValue:@(0) forKey:@”age”];
} else {
[super setNilValueForKey:key];
}
}
注
为了向后兼容性,当一个对象覆盖了已废弃的unableToSetNilForKey:
方法时,setValue:forKey:
调用该方法,而不是setNilValueForKey:
。
Adding Validation
KVC 定义了用于验证 key 或 key-path 的方法. 默认实现依赖于你定义的如下方法. 注意, 你需要为你想验证的以 key命名的属性提供 valudate
方法. 响应 validateValue:forKey:error:
消息的默认实现会查找这个方法.
如果你不为属性提供验证方法, 协议默认实现假设验证成功, 不管它的值. 这意味着你选择在逐个属性的基础上进行验证。
Implementing a Validation Method
当你为属性提供了验证方法, 方法接收两个引用参数: 需要验证的值对象和 NSError 用来返回 error 信息. 作为结果, 验证方法会有三种结果:
- 当验证对象验证有效, 不改变对象 和 error, 返回 YES.
- 当验证对象验证无效, 且你要么不能,要么不想提供一个有效的替代方案,设置 error 参数指向一个 NSError 对象指示失败的原因并且返回 NO.
重要
总是在设置 error 值的时候判断是否为 NULL. - 当验证对象验证无效, 但是你知道一个有效的替代方案, 创建有效对象, 赋值新对象, 返回 YES 且不修改 error. 如果提供了另外一个值, 总是返回一个新对象,而不是修改正在验证的对象,即使原始对象是可变的。
Validation method for the name property:
- (BOOL)validateName:(id *)ioValue error:(NSError * __autoreleasing *)outError{
if ((*ioValue == nil) || ([(NSString *)*ioValue length] < 2)) {
if (outError != NULL) {
*outError = [NSError errorWithDomain:PersonErrorDomain
code:PersonInvalidNameCode
userInfo:@{ NSLocalizedDescriptionKey
: @"Name too short" }];
}
return NO;
}
return YES;
}
Validation of Scalar Values
验证方法期望value参数是一个对象,因此,非对象属性的值被包装在一个NSValue或NSNumber对象中:
Validation method for a scalar property:
- (BOOL)validateAge:(id *)ioValue error:(NSError * __autoreleasing *)outError {
if (*ioValue == nil) {
// Value is nil: Might also handle in setNilValueForKey
// 可能 被 `setNilValueForKey` 处理
*ioValue = @(0);
} else if ([*ioValue floatValue] < 0.0) {
if (outError != NULL) {
*outError = [NSError errorWithDomain:PersonErrorDomain
code:PersonInvalidAgeCode
userInfo:@{ NSLocalizedDescriptionKey
: @"Age cannot be negative" }];
}
return NO;
}
return YES;
}
Describing Property Relationships
Class descriptions 提供类的属性 to-one 和 to-many 的描述. 定义类属性之间的这些关系允许使用 KVC 对这些属性进行更智能和灵活的操作。
Class Descriptions
NSClassDescription 是一个基本类提供了获取类的 meta-data 的接口. NSClassDescription 对象记录特定类对象的可用属性以及该类对象与其他对象之间的关系(one-to-one、one-to-many和反向)。比如 attributeKeys
方法返回一个类定义的属性列表, toManyRelationshipKeys
and toOneRelationshipKeys
方法返回定义 to-many 或 to-one 关系的 key 的集合. and inverseRelationshipKey:
方法返回参数 key 指定的目标关系指向接收者的关系名.
NSClassDescription 没有提供申明关系的方法, 子类必须定义这些方法, 创建后,可以使用NSClassDescription 的 registerClassDescription:forClass:
类方法注册一个类描述。
NSScriptClassDescription 是 NSClassDescription 的唯一子类, 它封装了应用程序的脚本信息。
Designing for Performance
KVC 的效率是很高的, 尤其是在使用默认实现工作的基础上, 但是它确实增加了一个间接级别,比直接方法调用稍微慢一些. 当您可以从它提供的灵活性中获益,或者允许您的对象参与依赖于它的Cocoa技术时才会使用 KVC.
Overriding Key-Value Coding Methods
通常, 使用 KVC 会使对象继承 NSObject, 提供特定于属性的访问器和相关方法。很少需要覆盖 KVC 访问方法的默认实现,比如 valueForKey:
and setValue:forKey:
或 validateValue:forKey:
, 如果重写的话, 需要调用 super, 因为它缓存了运行环境的信息提高效率.
Optimizing To-Many Relationships
访问方法的索引形式在许多情况下提供了显著的性能提升,特别是对于可变集合。 Accessing Collection Properties and Defining Collection Methods.
Compliance Checklist
Attribute and To-One Relationship Compliance
For each property that is an attribute or a to-one relationship:
- 实现一个方法
oris
, 或者创建一个实例变量
or_
. 自动合成的话编译器自动实现. - 如果属性时可变的, 实现
set
方法, 自动合成的话编译器自动实现.: 如果重写 setter, 不要执行任何协议的验证方法.
- 如果属性时标量, 重写
setNilValueForKey:
方法更好的处理为其赋值 nil 的情况.
Indexed To-Many Relationship Compliance (有序集)
For each property that is an ordered, to-many relationship (such as an NSArray object):
- 实现返回 array 的
方法, 后者有一个实例变量
or_
. 自动合成编译器自动实现. - 或实现
countOf
和objectIn
andAtIndex:
其中一个.AtIndexes: - 实现可选方法
get
提高性能.:range:
如果时可变属性,需要额外实现: -
insertObject:in
andAtIndex: insert
的一个或全部.:atIndexes: -
removeObjectFrom
andAtIndex: remove
的一个或全部.AtIndexes: - 可选
replaceObjectIn
orAtIndex:withObject: replace
提高性能.AtIndexes:with :
Unordered To-Many Relationship Compliance (无序集)
For each property that is an unordered, to-many relationship (such as an NSSet object):
- 实现返回 set 的
方法, 或者一个
or_
的 NSSet 实例变量. 可自动合成. - 或实现
countOf
,enumeratorOf
, andmemberOf
方法.:
如果是可变属性, 需要额外实现: -
add
andObject: add
一个或全部.: -
remove
andObject: remove
一个或全部.: - 可选
intersect
提高性能.:
Validation
Opt in to validation for properties that need it:
- 实现
validate
方法, 返回一个 Boolean 值表示验证值的有效性, 合适的情况下引用 error.:error: