< Previous PageNext Page > Hide TOC

Key-Value Validation

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.

Contents:

Validation Method Naming Convention
Implementing a Validation Method
Invoking Validation Methods
Automatic Validation
Validation of Scalar Values
Validation Methods and Memory Management


Validation Method Naming Convention

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 ...;
}

Implementing a Validation Method

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:

  1. The object value is valid, so YES is returned without altering the value object or the error.

  2. 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.

  3. 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”).

Invoking Validation Methods

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.

!

Warning: An implementation of -set<Key>: for a property should never call the validation methods.

Automatic Validation

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 of Scalar Values

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;
    }
    // ...

Validation Methods and Memory Management

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
}


< Previous PageNext Page > Hide TOC


© 2003, 2009 Apple Inc. All Rights Reserved. (Last updated: 2009-02-04)


Did this document help you?
Yes: Tell us what works for you.
It’s good, but: Report typos, inaccuracies, and so forth.
It wasn’t helpful: Tell us what would have helped.