views:

619

answers:

1

Hello,

I have my data in a NSDictionary object where the keys are CGPoints converted to NSValues and the objects are UIColors. Here's the method I'm using to return an object from the dictionary:

- (UIColor*) getTemperatureColor2 {
    NSDictionary* temperatureColorMap = [Weather getTemperatureColorMap];   

    for(id key in temperatureColorMap) {
        CGPoint point = [key CGPointValue];
        if ( (int)roundf(self.temperature_celsius) >= (int)roundf(point.x)  ) { 
            if ( (int) roundf(self.temperature_celsius) <= (int) roundf(point.y) ) {
                return [temperatureColorMap objectForKey:key];
            }
        }       
    }

    return [UIColor blackColor];    
}

This is the getTemperatureColorMap method, implemented in this same class (Weather):

+ (NSDictionary*) getTemperatureColorMap {
    static NSDictionary* temperatureColorMap = nil;

    if (temperatureColorMap == nil) {
        temperatureColorMap = [[[NSDictionary alloc] initWithObjectsAndKeys:
                            RGB2UIColor(0x0E09EE), [NSValue valueWithCGPoint: CGPointMake(-99, -8)],
                            RGB2UIColor(0xB85FC), [NSValue valueWithCGPoint:  CGPointMake(-7, -3) ],
                            RGB2UIColor(0x0BDCFC), [NSValue valueWithCGPoint: CGPointMake(-2, 2) ],
                            RGB2UIColor(0x1BBA17), [NSValue valueWithCGPoint: CGPointMake(3, 7) ],
                            RGB2UIColor(0x45F90C), [NSValue valueWithCGPoint: CGPointMake(8, 12) ],
                            RGB2UIColor(0xF9F60C), [NSValue valueWithCGPoint: CGPointMake(13, 17) ],
                            RGB2UIColor(0xF9B20C), [NSValue valueWithCGPoint: CGPointMake(18, 22) ],
                            RGB2UIColor(0xF9780C), [NSValue valueWithCGPoint: CGPointMake(23, 27) ],
                            RGB2UIColor(0xFE3809), [NSValue valueWithCGPoint: CGPointMake(28, 32) ],
                            RGB2UIColor(0xFE0909), [NSValue valueWithCGPoint: CGPointMake(33, 99) ], nil] autorelease];
    }

    return temperatureColorMap;
}

I call getTemperatureColor2 in a for loop (going through all the waypoints), which is all in the drawRect method. A waypoint contains a weather object.

routeAnnotation.lineColor = [fromWaypoint.weather getTemperatureColor2];

When the view loads, the drawRect method is called twice (I need this for an effect). The first time everything is fine, but the second time as soon as the code reaches the fast enumeration for loop I get an exception:

2010-01-15 11:40:42.224 AppName[1601:207] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[Waypoint countByEnumeratingWithState:objects:count:]: unrecognized selector sent to instance 0x856d170'

Now I have no idea how the error is in Waypoint as it's a NSDictionary that I'm iterating through. Also, I absolutely don't understand why it takes another call to drawRect for the iteration to fail!

+3  A: 

You want to perform fast enumeration on the keys in the dictionary like this:

for(NSValue *key in [temperatureColorMap allKeys])

UPDATE
Although my suggestion makes the intent more clear, it is definitely not the cause of exception that you are seeing (I realize now that NSDictionary implements fast enumeration and it must be on the array of keys).

I am now thinking it might be a memory error since you are autoreleasing the dictionary (but the static reference to it doesn't get set to nil when it's released), but I can not reproduce the exception even running your method many times.

The only difference between my code and yours is that I changed the call to RGB2UIColor into a call to an objective-C method instead.
You didn't provide its implementation, but can I assume it is able to return a proper objective-C UIColor object?

gerry3
I think you might be right, I'm still perplexed by how to deal with memory management in some situations. I know that I must release the temperatureColorMap, but how do I know when it is no longer needed? Where in the code should I send it the release message?
Leonard
Typically, if you need an object around for the duration of the life of another object (such as a view controller), you would make it an instance variable and/or property of the other object (and release it in the other object's dealloc method).
gerry3
I run my code again and I got a BAD_EXEC error - I'm sorry, I must have confused something. Instead of autoreleasing the temperatureColorMap I am now releasing it manually in getTemperatureColor2 after the for loop is done - so this is the point in code where the temperature map is no longer needed. Works!I am familiar with handling instance variables, I usually don't know what to do with objects that are created somewhere in code and then passed as arguments to some other method in some other class.
Leonard
Typically, in the other class, you would retain the object for as long as it is needed. That could be accomplished by either assigning it to a retain property or to an instance variable and retaining it. When it's no longer needed, set the propert to nil or release the instance var.
gerry3