Crash after dropping pins on map in iOS6-Collection of common programming errors
I am trying to learn how to drop pins on a map in iOS 6. I have a code which compiles and runs but which obviously leaks memory — but when I release (or autoRelease) the mapData object my app crashes. The error is
An instance 0x1b7ac0 of class AddressAnnotation was deallocated while key value observers were still registered with it. Observation info was leaked, and may even become mistakenly attached to some other object. Set a breakpoint on NSKVODeallocateBreak to stop here in the debugger.
There is an earlier post on just this error, also with respect to MapKit:
Setting breakpoint at NSKVODeallocateBreak
But it does not help me:
First, I don’t really understand the answer, but also it seems the answer is not relevant to my problem because I am not setting an observer in any way (that I know of, that is!) For example, nowhere in my code do I have the lines
[addressAnnotation addObserver:self forKeyPath:kSelectedAnnotationObserverKeyPath options:NSKeyValueObservingOptionNew context:@"selectedOrDeselected"];
or anything else remotely similar, which were suggested to be the problem.
Having said that, I should also say I do not really understand the concept of the observer — I have of course created a custom class MapData, which is an NSObject and I suppose that this could also be a source of the problem. But I am basically plumb stupefied.
I have tried to set the suggested symbolic break point but it is not helpful to me: I see I have a BAD ACCESS condition but that is all I really understand!
The code I have written is this:
- (void) showRecordsOnMap
{
NSMutableArray *projectMapAnnotationsArray;
projectMapAnnotationsArray = [[NSMutableArray alloc] init];
int i = 0;
for (i = 0; i < [currentProject.recordArray count]; i++)
{
Record *record = [[[Record alloc] init]autorelease];
record = [currentProject.recordArray objectAtIndex:i];
CLLocationCoordinate2D newCoordinate;
newCoordinate.latitude = record.latitude;
newCoordinate.longitude = record.longitude;
int tag = 0;
NSString *title;
title = [[[NSString alloc] init] autorelease];
title = [NSString stringWithFormat:@"Record %d",record.record_ID];
NSString *subtitle;
subtitle = [[[NSString alloc] init] autorelease];
subtitle = [NSString stringWithFormat:@"Record %d",record.record_ID];
MapData *mapData =[[MapData alloc] initWithCoordinate:newCoordinate withTag:tag withTitle:title withSubtitle:title];
[projectMapAnnotationsArray addObject:mapData];
//[mapData release];
}
[projectMap addAnnotations:projectMapAnnotationsArray];
[projectMapAnnotationsArray release];
}
and then the next needed bit
- (MKAnnotationView *) mapView:(MKMapView *)mapView
viewForAnnotation:(MapData *)annotation
{
static NSString *record = @"record";
//the result of the call is being cast (MKPinAnnotationView *) to the correct
//view class or else the compiler complains
MKPinAnnotationView *annotationView = (MKPinAnnotationView *)[projectMap
dequeueReusableAnnotationViewWithIdentifier:record];
if(annotationView == nil)
{
annotationView = [[[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:record] autorelease];
}
//if((annotation).tag == 2) annotationView.pinColor = MKPinAnnotationColorRed;
//else annotationView.pinColor = MKPinAnnotationColorGreen;
annotationView.pinColor = MKPinAnnotationColorGreen;
//pin drops when it first appears
annotationView.animatesDrop=TRUE;
//tapping the pin produces a gray box which shows title and subtitle
annotationView.canShowCallout = YES;
return annotationView;
}
This code runs, so long as the mapData object is not released. But clearly I need to release it. As another clue, if I uncomment
// if((annotation).tag == 2) annotationView.pinColor = MKPinAnnotationColorRed;
// else annotationView.pinColor = MKPinAnnotationColorGreen;
I get another error:
[MKUserLocation tag]: unrecognized selector sent to instance 0x9fcb010 2013-05-22 23:05:13.726 Geo360[1175:c07] * Terminating app due to uncaught exception
'NSInvalidArgumentException', reason: ‘-[MKUserLocation tag]:unrecognized selector sent to instance0x9fcb010‘
but it seems to me that this second error is a simpler stupidity on my part that I at least know how to go about finding. But the error “class AddressAnnotation” has me totally lost. Any help is greatly appreciated!
Edit:
Hi All — Thank you for taking the time to help. I am still confused. Attached is the code for the MapData object as suggested by AnnaKarenina. Verbumdei suggested I put the array in ViewDidLoad method as a strong property — I had played with that but I also want to be able to refresh the map pins with an array that may have more data or fewer data, so it seemed to me I needed to make the array anew each time. Perhaps not? AnnaKarenina suggested there may be a release problem in MapData and now that I look at it I am a bit suspicious that I am not releasing tag — but on the other hand, doing so generates a warning!
Thank you again for helping… still not solved.
MapData.h:
#import
#import
#import
@interface MapData : NSObject
{
NSString *_title;
NSString *subtitle;
NSUInteger tag;
CLLocationCoordinate2D _coordinate;
}
@property (nonatomic, readonly) CLLocationCoordinate2D coordinate;
@property (nonatomic, copy) NSString *title;
@property (nonatomic, copy) NSString *subtitle;
@property(nonatomic) NSUInteger tag;
// Getters and setters
- (id)initWithCoordinate:(CLLocationCoordinate2D)c withTag:(NSUInteger)t withTitle:(NSString *)tl withSubtitle:(NSString *)s;
@end
and MapData.m:
#import "MapData.h"
@implementation MapData
@synthesize coordinate;
@synthesize title;
@synthesize subtitle;
@synthesize tag;
-(id)initWithCoordinate:(CLLocationCoordinate2D)c withTag:(NSUInteger)t withTitle:(NSString *)tl withSubtitle: (NSString *)s
{
if(self = [super init])
{
coordinate = c;
tag = t;
title = tl;
subtitle = s;
}
return self;
}
- (void) dealloc
{
[title release];
[subtitle release];
[super dealloc];
}
@end
-
Regarding the crash with EXC_BAD_ACCESS, this is most likely due to this code in the MapData
initWithCoordinatemethod:title = tl; subtitle = s;By initializing the instance variables this way, the strings are not retained by the MapData object. When you call
[mapData release], the strings are deallocated and then later when the map view tries to access the annotation’s title and subtitle, it crashes.Change the initialization to:
title = [tl copy]; subtitle = [s copy];and un-comment the
[mapData release];Regarding the “deallocated while key value observers were still registered…” warning message, this is possibly due to invalid coordinates being used for an annotation (see Warning in Custom Map Annotations iPhone). For each annotation, make sure the latitude is from -90 to 90 and the longitude is from -180 to 180.
Regarding the “[MKUserLocation tag]: unrecognized selector” crash, this is unrelated to both of the above issues. This error happens because the
viewForAnnotationdelegate method is called by the map view for all annotations — not just the ones you add. This means it is also called for the user location blue dot that the map view itself creates. That user location annotation is of typeMKUserLocationwhile your custom annotation is of typeMapData. When the map view callsviewForAnnotationfor the user location, that code crashes because theMKUserLocationclass does not have atagproperty.The simplest way to handle this is at the top of the
viewForAnnotationmethod, check if the annotation is of typeMKUserLocationand returnnilfor the view (which tells the map view to display the default view which is a blue dot for the user location).Also, do not change the type declaration of the
annotationparameter in theviewForAnnotationmethod to your custom type (even though it “works”). As explained, the method is called forMKUserLocationas well as your custom class (or classes). Keep it the generic typeidwhich means “an object that implements theMKAnnotationprotocol”. Then, to access properties of your custom class, castannotationto your custom class type.So the top of the
viewForAnnotationmethod should be like this:- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id)annotation { if ([annotation isKindOfClass:[MKUserLocation class]]) return nil; //show standard blue dot view for user locationThen change these lines:
if((annotation).tag == 2) annotationView.pinColor = MKPinAnnotationColorRed; else annotationView.pinColor = MKPinAnnotationColorGreen;to this:
if ([annotation isKindOfClass:[MapData class]]) { MapData *mapData = (MapData *)annotation; if (mapData.tag == 2) annotationView.pinColor = MKPinAnnotationColorRed; else annotationView.pinColor = MKPinAnnotationColorGreen; }There are also a few other things (not related to the crash or errors):
-
This pattern is wrong:
Record *record = [[[Record alloc] init]autorelease]; record = [currentProject.recordArray objectAtIndex:i];The alloc+init+autorelease is pointless because the variable is then immediately reassigned to another object which has already been allocated. Instead, just declare and assign the local variable:
Record *record = [currentProject.recordArray objectAtIndex:i];The same applies to how
titleandsubtitleare set in the same method. -
In
showRecordsOnMap,tagis always0. Perhaps your code isn’t finished yet but be sure to set a different value for each annotation. If you leave the tag as zero for all annotations, the code you put inviewForAnnotationto set the pin color based on tag will not work as expected. -
In this line you are passing
titlefor both the title and subtitle parameters:MapData *mapData =[[MapData alloc] initWithCoordinate:newCoordinate withTag:tag withTitle:title withSubtitle:title]; -
In
viewForAnnotation, after dequeuing an existing annotation view, you should update the re-used view’sannotationproperty to the current one (add theelsepart):if (annotationView == nil) { annotationView = [[[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:record] autorelease]; } else { //We're re-using a view from another annotation //that's no longer on screen. //Update the view's annotation to the current one... annotationView.annotation = annotation; }
-
-
While the annotations are shown in the map, you must not release the MapData objects. If you want to release the MapData objects, you need to remove the annotations first from the map.
I would suggest you make the projectMapAnnotationsArray as a strong property of the view controller. Allocate it in the
viewDidLoadmethod. Then you can just release it in thedeallocmethod of the view controller.Inside the
showRecordsOnMapmethod, you can just add the objects to the projectMapAnnotationsArray without releasing the array itself.
Originally posted 2013-11-27 12:25:38.