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
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 typeMKUserLocation
while your custom annotation is of typeMapData
. When the map view callsviewForAnnotation
for the user location, that code crashes because theMKUserLocation
class does not have atag
property.The simplest way to handle this is at the top of the
viewForAnnotation
method, check if the annotation is of typeMKUserLocation
and returnnil
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 theviewForAnnotation
method to your custom type (even though it “works”). As explained, the method is called forMKUserLocation
as well as your custom class (or classes). Keep it the generic typeid
which means “an object that implements theMKAnnotation
protocol”. Then, to access properties of your custom class, castannotation
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
andsubtitle
are set in the same method. -
In
showRecordsOnMap
,tag
is 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 inviewForAnnotation
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’sannotation
property to the current one (add theelse
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; }
-
-
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 thedealloc
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.