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 instance 0x9fcb010

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
  1. Regarding the crash with EXC_BAD_ACCESS, this is most likely due to this code in the MapData initWithCoordinate method:

    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 viewForAnnotation delegate 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 type MKUserLocation while your custom annotation is of type MapData. When the map view calls viewForAnnotation for the user location, that code crashes because the MKUserLocation class does not have a tag property.

    The simplest way to handle this is at the top of the viewForAnnotation method, check if the annotation is of type MKUserLocation and return nil for 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 annotation parameter in the viewForAnnotation method to your custom type (even though it “works”). As explained, the method is called for MKUserLocation as well as your custom class (or classes). Keep it the generic type id which means “an object that implements the MKAnnotation protocol”. Then, to access properties of your custom class, cast annotation to your custom class type.

    So the top of the viewForAnnotation method 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 location
    

    Then 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 title and subtitle are set in the same method.

    • In showRecordsOnMap, tag is always 0. 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 in viewForAnnotation to set the pin color based on tag will not work as expected.

    • In this line you are passing title for 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’s annotation property to the current one (add the else part):

      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;
      }
      
  2. 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 viewDidLoad method. Then you can just release it in the dealloc method of the view controller.

    Inside the showRecordsOnMap method, you can just add the objects to the projectMapAnnotationsArray without releasing the array itself.

Originally posted 2013-11-27 12:25:38.