views:

616

answers:

2

What it says on the tin: I'd like to use the @property/@synthesize syntax to define a property on my Objective-C 2.0 class, but I want to place restrictions on the range of values allowed in the property. For example:

@interface MyClass : NSObject {
    int myValue;
}

@property (nonatomic) int myValue;

Implementation:

@implementation MyClass

@synthesize myValue(test='value >= 0');

Note that the syntax here is just an example. Is this, or something much like it possible? Alternately, what is the literal equivalent of a synthesized setter, so that I can ensure that I use the same object retention rules in my manual setters as is used in a synthesized one.

+3  A: 

When you use the @synthesize the accessor methods are generated. You can implement your own which will overwrite the generated one.

You can put your own implementation inside the accessor methods, e.g. you can add value checking before assignment and so on.

You can ommit one or the other or both, the ones that you don't implement will be generated because of @synthesize, if you use @dynamic you are specifying that you will provide accessors either at compile or run time.

Accessors will have names derived from the property name myproperty and setMyproperty. The method signatures are standard so it is easy to implement your own. The actual implementation depends on property definition (copy, retain, assign) and if it is read-only or not (read-only doesn't get set accessor). For more details see objective-c reference.

Apple reference:

@synthesize You use the @synthesize keyword to tell the compiler that it should synthesize the setter and/or getter methods for the property if you do not supply them within the @implementation block.

@interface MyClass : NSObject
{
    NSString *value;
}
@property(copy, readwrite) NSString *value;
@end


@implementation MyClass
@synthesize value;

- (NSString *)value {
    return value;
}

- (void)setValue:(NSString *)newValue {
    if (newValue != value) {
        value = [newValue copy];
    }
}
@end
stefanB
+5  A: 

Assuming your properties are Key-Value compliant (as they would be if you are using @synthesize) you should also implement Key-Value compliant validators. Take a look at Apple's documentation on the matter: http://developer.apple.com/documentation/Cocoa/Conceptual/KeyValueCoding/Concepts/Validation.html

The important thing to note is that validation does not happen automatically except when using certain kinds of binding. You either call the validator directly or by calling validateValue:forKey:error:.

You could override the produced setter to call the validator before saving it but if you are using bindings this is probably not what you want to do as the validator will possibly be called more than once for a single modification.

Also note that the validator might change the value being validated.

So lets look at your example (untested, btw. I'm not near a Mac):

@implementation MyClass

@synthesize myValue;

-(BOOL)validateMyValue:(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 intValue] < 0 ) {
    NSString *errorString = @"myValue must be greater than zero";
    NSDictionary *userInfoDict = [NSDictionary dictionaryWithObject:errorString
                                                             forKey:NSLocalizedDescriptionKey];

    NSError *error = [[[NSError alloc] initWithDomain:@"MyValueError"
                                                 code:0
                                             userInfo:userInfoDict] autorelease];

    *outError = error;

    return NO;
  } else {
    return YES;
  }
}

If you wanted to override the synthesised setter and make it do the validation (still untested):

- (void)setMyValue:(int)value {

  id newValue = [NSNumber numberWithInt:value];
  NSError *errorInfo = nil;

  if ( [self validateMyValue:&newValue error:&errorInfo] ) {
    myValue = [newValue intValue];
  }
}

You can see we had to wrap the integer in an NSNumber instance to do this.

toholio