非对象 value
的表示
NSObject
提供的KVC
协议方法的默认实现对对象和非对象属性都是有效的。默认的实现将自动转换对象参数或者返回值,以及非对象属性。这将允许基于key
的getters
和setters
的特征保持一致,即使存储的属性是基本数据变量或结构体。
因为Swift中所有的属性都是对象,这部分仅适用OC属性。
当调用协议的getters方法时,比如valueForKey:
,默认实现将根据KVC方法的搜索模式中描述的规则为指定的key
提供特定访问方法或者实例变量。如果返回的值不是对象,则getters会将值初始化为NSNumber
对象(对于基本数据类型)或者NSValue
(对于结构体)返回。
同样地,默认情况下,setter方法比如setValue:forKey:
根据特定的key
来确定属性的访问方法或者实例变量所需的数据类型。如果数据类型不是对象,setters首先向传入值(value
)发送适当的<type>Value
消息以获取底层数据,然后将其存储。
注意:
当你调用kvc协议中的一个方法为非对象属性设置一个
nil
值时,它会向收到setter调用的对象发送一个setNilValueForKey:
消息。该方法的默认实现会抛出NSInvalidArgumentException
异常,但子类可以覆盖该方法。
基本数据类型的Wrap 和 Unwrap
下表列出了默认KVC
实现中使用NSNumber
实例包装的基本数据类型。
Data type | Creation method | Accessor method |
---|---|---|
BOOL |
numberWithBool: |
boolValue (in iOS) </br>charValue (in macOS)* |
char |
numberWithChar: |
charValue |
double |
numberWithDouble: |
doubleValue |
float |
numberWithFloat: |
floatValue |
int |
numberWithInt: |
intValue |
long |
numberWithLong: |
longValue |
long long |
numberWithLongLong: |
longLongValue |
short |
numberWithShort: |
shortValue |
unsigned char |
numberWithUnsignedChar: |
unsignedChar |
unsigned int |
numberWithUnsignedInt: |
unsignedInt |
unsigned long |
numberWithUnsignedLong: |
unsignedLong |
unsigned long long |
numberWithUnsignedLongLong: |
unsignedLongLong |
unsigned short |
numberWithUnsignedShort: |
unsignedShort |
结构体的Wrap 和 Unwrap
Data type | Creation method | Accessor method |
---|---|---|
CGPoint |
valueWithCGPoint: |
CGPointValue |
NSRange |
valueWithRange: |
rangeValue |
CGRect |
valueWithCGRect: |
CGRectValue |
CGSize |
valueWithCGSize: |
CGSizeValue |
当然,还有其他的结构体,比如UIEdgeInsets
,CGVector
以及我们自定义的结构体等等,这里就不一一列举了。
验证属性
KVC协议定义了支持属性验证的方法,就像使用基于key
的访问方法来读写属性一样,我们也可以通过key
(或keypath
)来验证属性。 当调用validateValue:forKey:error:
(或validateValue:forKeyPath:error:
)方法,协议的默认实现是查找接收到验证消息的对象(或者keypath
中结尾的对象)的名称匹配validate<Key>:error:
的方法。如果对象没有该方法,默认情况下验证成功,当指定的属性的验证方法存在时,默认实现将返回该方法的结果。
注意:
通常仅在Objective-C中使用此处所述的验证。 在Swift中,属性验证通过依赖于编译器和强类型检查的支持来处理,同时使用内置的
willSet
和didSet
属性观察器来测试任何运行时API.
由于指定属性的验证方法通过引用value
和error
参数,验证有三种可能的结果:
-
验证方法认为
value
对象有效并返回YES
,(不会更改value
或error
) -
验证方法认为
value
对象无效,但并不改变它。这种情况,方法会返回NO
并且将error
参数(如果调用者提供了的话)设置为指示失败原因的NSError
对象 -
验证方法认为
value
对象无效,并且新创建一个有效的作为替代。 这种情况下,该方法在error
对象不变的情况下返回YES。在返回之前,该方法修改value
的引用以指向新的value
对象。当它进行修改时,该方法总是创建一个新的对象,而不是修改旧对象,即使value
对象是可变的。
validateValue:forKey:error:
- (BOOL)validateValue:(inout id _Nullable *)ioValue forKey:(NSString *)inKey error:(out NSError * _Nullable *)outError;
- ioValue: 指向由
inKey
标识的属性的新值的指针。 此方法可能会修改或替换该值以使其有效。- inKey:
receiver
的一个属性名称。必须是指定的Attribute
或者一个to-one
关系。- outError: 如果需要验证并且
ioValue
未转换为有效值,则在返回时包含描述ioValue
不是有效值的原因的NSError
对象。- 返回值: 如果
ioValue
指向的值对于由inKey
标识的属性有效,或者该方法能够修改ioValue
中的值使其有效,则返回YES; 否则为NO。
validateValue:forKey:error:
方法的默认实现
该方法的默认实现是查找receiver
类中方法名匹配-validate<Key>:error:
的方法,如果你为属性定义了这样的方法,当验证相应的属性时,validateValue:forKey:error:
的默认实现会调用-validate<Key>:error:
方法。
如果没有找到-validate<Key>:error:
方法,validateValue:forKey:error:
返回 YES
.
下面示例展示了如何为name
字符串调用验证的 示例:
Person* person = [[Person alloc] init];
NSError* error;
NSString* name = @"John";
BOOL validation = [person validateValue:&name forKey:@"name" error:&error];
if (validation) {
[person setValue:name forKey:@"name"];
}
else {
NSLog(@"error: %@",error);
}
Person类实现了name
的验证方法(这里设置了name
必须是长度大于5的字符串)
- (BOOL)validateName:(inout id _Nullable __autoreleasing *)ioValue error:(out NSError * _Nullable __autoreleasing *)outError
{
NSString *reason = nil;
if ([*ioValue isKindOfClass:[NSString class]]) {
NSString *name = (NSString *)*ioValue;
if (name.length > 5) {
return YES;
}
else {
reason = @"取名字必需大于5个字符";
}
}
else {
reason = @"name 必须是字符串";
}
if (outError != NULL) {
NSDictionary *userInfo = @{NSLocalizedFailureReasonErrorKey:reason};
*outError = [NSError errorWithDomain:NSLocalizedDescriptionKey code:10086 userInfo:userInfo];
}
return NO;
}
validateValue:forKeyPath:error:
- (BOOL)validateValue:(inout id _Nullable *)ioValue forKeyPath:(NSString *)inKeyPath error:(out NSError * _Nullable *)outError;
此方法的默认实现使用valueForKey
获取每个关系的目标对象,并返回为该属性调用validateValue:forKey:error:
方法的结果。
自动验证
通常,KVC
协议及其默认实现都不定义任何自动执行验证的机制。相反,我们可以在app适当的时候使用验证方法。
在某些情况下,某些其他Cocoa
技术会自动执行验证。 例如,Core Data
在保存管理对象上下文时自动执行验证(感兴趣的可以查看Core Data Programming Guide)。
参考:
网友评论