Key-value coding provides a consistent API for validating a property value. The validation infrastructure provides a class the opportunity to accept a value, provide an alternate value, or deny the new value for a property and give a reason for the error.
Validation Method Naming Convention
Implementing a Validation Method
Invoking Validation Methods
Automatic Validation
Validation of Scalar Values
Validation Methods and Memory Management
Just as there are conventions for naming accessor methods, there is a convention for naming a property’s validation method. A validation method has the format validate<Key>:error:
. The example in Listing 1 shows the validation method declaration for the property name
.
Listing 1 Validation method declaration for the property name
-(BOOL)validateName:(id *)ioValue error:(NSError **)outError { |
// implementation specific code |
return ...; |
} |
Validation methods are passed two parameters by-reference, the value object to validate, and the NSError used to return error information.
There are three possible outcomes from a validation method:
The object value is valid, so YES
is returned without altering the value object or the error.
A new object value that is valid is created and returned. In this case YES
is returned after setting the value parameter to the newly created, valid value. The error is returned unaltered. You must return a new object, rather than just modifying the passed ioValue, even if it is mutable.
The object value is not valid and a valid value cannot be created and returned. In this case NO
is returned after setting the error parameter to an NSError object that indicates the reason validation failed.
The example in Listing 2 implements a validation method for a name
property that ensures that the value object is a string and that the name is capitalized correctly.
Listing 2 Validation method for the name property
-(BOOL)validateName:(id *)ioValue error:(NSError **)outError |
{ |
if (*ioValue == nil) { |
return YES; |
} |
// enforce capitalization |
NSString *capitalizedName = [*ioValue capitalizedString]; |
*ioValue = capitalizedName; |
return YES; |
} |
Important: A validation method that returns NO
must first check that the outError parameter is not NULL
. It must then ensure that the outError parameter is set to a valid NSError object before returning NO
.
Note that the object returned in ioValue
is autoreleased (see “Validation Methods and Memory Management”).
You can call validation methods directly, or by invoking validateValue:forKey:error:
and specifying the key. The default implementation of validateValue:forKey:error: searches the class of the receiver for a validation method whose name matches the pattern validate<Key>:error:
. If such a method is found, it is invoked and the result is returned. If no such method is found, validateValue:forKey:error: returns YES
, validating the value.
Key-value coding does not perform validation automatically. It is, in general, your application’s responsibility to invoke the validation methods.
Some Cocoa bindings allow you to specifiy that validation should occur automatically, see Cocoa Bindings Programming Topics for more information. Core Data automatically performs validation when the managed object context is saved.
Validation methods expect the value parameter to be an object, and as a result values for non-object properties are wrapped in an NSValue or NSNumber object as discussed in “Scalar and Structure Support.” The example in Listing 3 demonstrates a validation method for the scalar property age
.
Listing 3 Validation method for a scalar property
-(BOOL)validateAge:(id *)ioValue error:(NSError **)outError |
{ |
if (*ioValue == nil) { |
// trap this in setNilValueForKey |
// alternative might be to create new NSNumber with value 0 here |
return YES; |
} |
if ([*ioValue floatValue] <= 0.0) { |
NSString *errorString = NSLocalizedStringFromTable( |
@"Age must be greater than zero", @"Person", |
@"validation: zero age error"); |
NSDictionary *userInfoDict = |
[NSDictionary dictionaryWithObject:errorString |
forKey:NSLocalizedDescriptionKey]; |
NSError *error = [[[NSError alloc] initWithDomain:PERSON_ERROR_DOMAIN |
code:PERSON_INVALID_AGE_CODE |
userInfo:userInfoDict] autorelease]; |
*outError = error; |
return NO; |
} |
else { |
return YES; |
} |
// ... |
Since validation methods can replace both the original value object and the error object that are passed by-reference, care must be taken to ensure proper memory management. Your application must ensure that objects that it creates and passes as parameters to the validation method have been autoreleased before invoking the method.
As an example, the code in Listing 4 invokes the validateName:error:
method that was shown in Listing 2. It creates the newName
object, and passes it to the validateName:error:
method without autoreleasing the object. However, because the validation method replaces the object that newName
references, when the newName
object is explicitly released, the validated object is released instead. This causes two problems. Attempting to access the validated name value later will cause a crash, because the object that is retained by the setValue:forKey:
has been released, and the name object passed to the validateName:error:
method leaks.
Listing 4 Incorrect invocation of validateName:error:
NSString *newName; |
NSError *outError; |
newName = [[NSString alloc] initWithString:@"freddy"]; |
if ([person validateName:&newName error:&outError]) { |
// set the value, which will retain |
// the newName object |
[person setValue:newName forKey:@"name"]; |
} |
else { |
// inform the user that the value is invalid |
} |
[newName release]; |
The example in Listing 5 prevents these problems by autoreleasing the newName
object before invoking the validateName:error:
method. The original object is freed by the autorelease, and the validated object is retained by the receiver as a result of the setValue:forKey:
message.
Listing 5 Correct invocation of validateName:error:
NSString *newName; |
NSError *outError; |
newName = [[[NSString alloc] initWithString:@"freddy"] autorelease]; |
if ([person validateName:&newName error:&outError]) { |
// set the value, which will retain |
// the newName object |
[person setValue:newName forKey:@"name"]; |
} |
else { |
// inform the user that the value is invalid |
} |
© 2003, 2009 Apple Inc. All Rights Reserved. (Last updated: 2009-02-04)