correct way to send messages to (id *) variables or with (id *) arguments
I have a Core Data validation method I wrote that will not compile. I can modify the method so it compiles... but then I get runtime errors. The two versions of the method are below (notice the missing '*' in the second version). This version compiles but gives the runtime error "+[NSCFNumber doubleValue]: unreco开发者_如何学Cgnized selector sent to class 0x7fff70a448e8":
- (BOOL)validateInitialValue:(id *)value error:(NSError **)error {
if ( *value == nil ) {
return YES;
}
if ( [*value doubleValue] < 0.0 ) {
return NO;
}
return YES;
}
This version gives compiler warnings and errors (see warnings and errors below):
- (BOOL)validateInitialValue:(id *)value error:(NSError **)error {
if ( value == nil ) {
return YES;
}
if ( [value doubleValue] < 0.0 ) {
return NO;
}
return YES;
}
compiler errors and warnings:
warning: Semantic Issue: Receiver type 'id *' is not 'id' or interface pointer, consider casting it to 'id'
warning: Semantic Issue: Method '-doubleValue' not found (return type defaults to 'id')
error: Semantic Issue: Invalid operands to binary expression ('id' and 'double')
I finally figured that the problem may be in the calling code:
- (void)setInitialValue:(NSNumber *)initialValue {
[self willChangeValueForKey:@"initialValue"];
[self validateValue:initialValue forKey:@"initialValue" error:nil];
[self setPrimitiveInitialValue:initialValue];
[self didChangeValueForKey:@"initialValue"];
}
I changed the caller to use an '&' before initialValue and used the first version of the method, and everything worked. So the new calling code has the one line changed to be this:
[self validateValue:&initialValue forKey:@"initialValue" error:nil];
But is it really necessary to have the '&'?? setPrimitiveInitialValue doesn't use the '&'. I feel like my understanding of Objective-C is just not developed enough yet and all you gurus out there will find this a trivial question with a very straight forward answer.
id
itself represents a pointer. So when you use id *
you are actually referring to a pointer-to-a-pointer. The first part of this excellent tutorial explains this concept.
Chances are, this is what you are looking for:
- (BOOL)validateInitialValue:(id)value error:(NSError **)error {
if ( value == nil ) {
return YES;
}
if ( [value doubleValue] < 0.0 ) {
return NO;
}
return YES;
}
You're right that the problem is the calling code. id *
indicates a pointer to an id
value. An object variable by itself is an id
, so you want a pointer to that variable, which is what you get with the &
.
The reason you pass a pointer is so that, if your validation method knows of a way to modify the value to make it valid, it can return YES
and also return the valid object (by setting the variable). So, for example, if numbers less than 1 should be clamped to 0, you might do:
- (BOOL)validateInitialValue:(id *)value error:(NSError **)error {
if ( *value == nil ) {
return YES;
}
if ( [*value doubleValue] < 0.0 ) {
return NO;
}
if ( [*value doubleValue] > 0.0 && [*value doubleValue] < 1.0 ) {
*value = [NSNumber numberWithInt:0];
}
return YES;
}
setPrimitiveValue:
doesn't need to set variables in the calling context, so it just takes an id
. (Very few methods work like validateValue:forKey:error:
. Generally, they'll do it that way if they want to return a BOOL
to indicate whether they changed something, but they still need a way to return the changed value as well.)
精彩评论