I received an SOS from a mate asking to see why his $1,000,000 app making idea was crashing at the first hurdle.
Terminating app due to uncaught exception ‘NSInvalidArgumentException’, reason: ‘-[MyAnnotation objectForKey:]: unrecognized selector sent to instance 0x73be080’
If there is one thing the last three years of xCode development has taught me, it’s that error messages are ambiguous, misleading or downright evil.
The actual message is simple, “unrecognized selector sent to instance”, so we have called a method that doesn’t exist on the class. But then why did it work 7 times prior ? Surely if we are running in a simple loop doing the same thing we shouldn’t expect different results?
Generally when I receive this type of message it means that I have an array of {NSObject}, and I am assuming the object is of a specific type. However somewhere along the line something else has snuck in.
for(int i = 0; i < [anns count]; i++) {
float realLatitude = [[[anns objectAtIndex:i] objectForKey:@"latitudeKey"] floatValue];
float realLongitude = [[[anns objectAtIndex:i] objectForKey:@"longitudeKey"] floatValue];
MyAnnotation* myAnnotation = [[MyAnnotation alloc] init];
CLLocationCoordinate2D theCoordinate;
theCoordinate.latitude = realLatitude;
theCoordinate.longitude = realLongitude;
myAnnotation.coordinate = theCoordinate;
myAnnotation.title = [[anns objectAtIndex:i] objectForKey:@"cityNameKey"];
myAnnotation.subtitle = [[anns objectAtIndex:i] objectForKey:@"subtitle"];
[mapView addAnnotation:myAnnotation];
[anns addObject:myAnnotation];
[myAnnotation release];
}
Looking at the above code, we can see that the array “anns” is being mutated within the loop, the author is adding the annotation object into the array of dictionary, then on the eighth call (7 original elements) our code is dealing with the annotation object instead, which does not have the “objectForKey” method (thankfully).
Since we have the code, let’s refactor it as well.
Firstly, we replace the loop construct and set up the NSDictionary object iterating our collection.
Then we replace the [[anns objectAtIndex:i] objectForKey:@"xyz"]
entries with the strongly typed object. In my opinion making the code safer and more readable. More importantly, removing the offending line that adds elements back into the collection.
Interestingly, if we were to leave the ‘bad’ line in, and run the program, we would receive a different message, just because of the change to the loop construct.
Terminating app due to uncaught exception ‘NSGenericException’, reason: ‘*** Collection was mutated while being enumerated.’
Which at least points us to the real cause of the original error !
The working code:
for(NSDictionary *note in anns) {
float realLatitude = [[note objectForKey:@"latitudeKey"] floatValue];
float realLongitude = [[note objectForKey:@"longitudeKey"] floatValue];
MyAnnotation* myAnnotation = [[MyAnnotation alloc] init];
CLLocationCoordinate2D theCoordinate;
theCoordinate.latitude = realLatitude;
theCoordinate.longitude = realLongitude;
myAnnotation.coordinate = theCoordinate;
myAnnotation.title = [note objectForKey:@"cityNameKey"];
myAnnotation.subtitle = [note objectForKey:@"subtitle"];
[mapView addAnnotation:myAnnotation];
[anns addObject:myAnnotation];
[myAnnotation release];
}