开发者

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

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜