Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I validate a @property value in Objective-C using @synthesized methods?

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.

like image 655
Chris R Avatar asked Jun 12 '09 02:06

Chris R


People also ask

What is @property in Objective C?

The goal of the @property directive is to configure how an object can be exposed. If you intend to use a variable inside the class and do not need to expose it to outside classes, then you do not need to define a property for it. Properties are basically the accessor methods.

What is @synthesize in Objective C?

Xcode will auto synthesise an iVar as if you had written... @synthesize name = _name; This means you can access the property with... self.name; // or _name; Either will work but only self.name actually uses the accessor methods.

What is synthesize in Swift?

Synthesized/auto-synthesized properties in Objective C -- these are called "stored properties" in Swift. You simply declare it with var topSpeed : Double or let topSpeed : Double = 4.2 in a class declaration, exactly as you would declare a local variable in a function body.

What is synthesize in IOS?

@synthesize tells the compiler to take care of the accessor methods creation i.e it will generate the methods based on property description. It will also generate an instance variable to be used which you can specify as above, as a convention it starts with _(underscore)+propertyName.


2 Answers

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
like image 45
stefanB Avatar answered Sep 21 '22 15:09

stefanB


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.

like image 63
toholio Avatar answered Sep 20 '22 15:09

toholio