MKAnnotationView crashing with EXEC_BAD_ACCESS
I know EXC_BAD_ACCESS is one of those things hard to nail down, and I know about zombie mode to track down deallocated objects, but I'm still having trouble with this one.
I have a custom MKAnnotation
, and I'm using the standard AnnotationView on my map. When I tap on a pin on my map, I crash. If I zoom in so only 1 pin is shown, it works. If I zoom back out and tap a new pin, it crashes.
开发者_开发问答What's driving me crazy is that this was working (as far as i know) 100% yesterday and I don't remember changing any code (admittedly I had been up for 24 hours so... its possible I just don't remember doing it.)
One time, I saw a pin give the title "com.apple.SOMETHING" before crashing. Sounds like an over release to me but the titles are stored in the annotations, I thought.
Zombie debugging gives:
*** -[CFString length]: message sent to deallocated instance 0x5b7b250.
Nowhere in my map view or annotation class do I use string length. I do have ONE photo with no title assigned, but it doesn't matter if that is the photo's pin that is tapped or visible on screen when the crash happens.
Here's the relevant code:
MapAnnotation.h
#import <Foundation/Foundation.h>
#import <MapKit/MapKit.h>
#import "Photo.h"
@interface MapAnnotation : NSObject <MKAnnotation> {
CLLocationCoordinate2D coordinate;
NSString *title;
NSString *subtitle;
Photo *photo;
}
-(id)initWithCoordinate:(CLLocationCoordinate2D)passCoordinate title:(NSString *)passTitle photo:(Photo *)passPhoto;
@property (nonatomic, readonly) CLLocationCoordinate2D coordinate;
@property (nonatomic, retain) NSString *title;
@property (nonatomic, retain) NSString *subtitle;
@property (nonatomic, retain) Photo *photo;
@end
MapAnnotation.m
#import "MapAnnotation.h"
@implementation MapAnnotation
@synthesize title;
@synthesize subtitle;
@synthesize photo;
@synthesize coordinate;
-(id)initWithCoordinate:(CLLocationCoordinate2D)passCoordinate title:(NSString *)passTitle photo:(Photo *)passPhoto
{
self = [super init];
if (self) {
// Custom initialization.
coordinate = passCoordinate;
title = passTitle;
photo = passPhoto;
}
return self;
}
- (void)dealloc {
[title release];
[photo release];
[super dealloc];
}
@end
MapViewController.h
#import <UIKit/UIKit.h>
#import <MapKit/MapKit.h>
#import "FlickrFetcher.h"
#import "Photo.h"
#import "Person.h"
#import "MapAnnotation.h"
#import "PhotoDetailViewController.h"
@interface MapViewController : UIViewController <MKMapViewDelegate> {
IBOutlet MKMapView *mapView;
FlickrFetcher *fetcher;
NSArray *fetchedObjects;
PhotoDetailViewController *photoDetail;
}
@property (retain, nonatomic) NSArray *fetchedObjects;
-(void)createAnnotations;
@end
And MapViewController.m
#import "MapViewController.h"
@implementation MapViewController
@synthesize fetchedObjects;
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
[super viewDidLoad];
[self.navigationController setNavigationBarHidden:YES animated:NO];
[mapView setDelegate:self];
fetcher = [FlickrFetcher sharedInstance];
// Setup a predicate that looks up all photos with non-zero latitide and longitude
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"longitude != 0 && latitude != 0"];
fetchedObjects = [fetcher fetchManagedObjectsForEntity:@"Photo" withPredicate:predicate];
[self createAnnotations];
}
- (void)viewWillAppear:(BOOL)animated
{
[self.navigationController setNavigationBarHidden:YES animated:YES];
}
-(void)createAnnotations
{
// iterate thorugh the array of photo objects, and setup annotation items, and put them on the map view
for ( Photo *currentPhoto in fetchedObjects )
{
MapAnnotation *annotation;
CLLocationCoordinate2D coordinate = {[[currentPhoto latitude] floatValue],[[currentPhoto longitude]floatValue]};
annotation = [[MapAnnotation alloc] initWithCoordinate:coordinate title:[currentPhoto name] photo:currentPhoto];
[mapView addAnnotation:annotation];
[annotation release];
}
}
#pragma mark -
#pragma mark Annotation View & Delegate Methods
- (MKAnnotationView *)mapView:(MKMapView *)thisMapView viewForAnnotation:(id <MKAnnotation>)annotation
{
MapAnnotation *myAnnotation = annotation;
MKPinAnnotationView *annotationView;
NSString* identifier = @"Pin";
MKPinAnnotationView* pin = (MKPinAnnotationView*)[thisMapView dequeueReusableAnnotationViewWithIdentifier:identifier];
if(nil == pin) {
pin = [[[MKPinAnnotationView alloc] initWithAnnotation:myAnnotation reuseIdentifier:identifier] autorelease];
}
pin.animatesDrop = YES;
annotationView = pin;
UIButton *annotationButton=[UIButton buttonWithType:UIButtonTypeDetailDisclosure];
annotationView.rightCalloutAccessoryView = annotationButton;
[annotationView setEnabled:YES];
[annotationView setCanShowCallout:YES];
return annotationView;
}
- (void)mapView:(MKMapView *)thisMapView annotationView:(MKAnnotationView *)annotationView calloutAccessoryControlTapped:(UIControl *)control
{
// Get the annoation from the annotation view that was tapped and make it into our custom protocol
MapAnnotation *annotation = annotationView.annotation;
Photo *photo = [annotation photo];
photoDetail = [[PhotoDetailViewController alloc] initWithNibName:@"PhotoDetailViewController" bundle:[NSBundle mainBundle]];
[photoDetail setPassedPhoto:photo];
photoDetail.title = [NSString stringWithFormat:@"%@", [photo name]];
[self.navigationController pushViewController:photoDetail animated:YES];
[self.navigationController setNavigationBarHidden:NO animated:YES];
[photoDetail release];
}
#pragma mark -
#pragma mark Memory Cleanup
- (void)didReceiveMemoryWarning {
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc. that aren't in use.
}
- (void)viewDidUnload {
[super viewDidUnload];
}
- (void)dealloc {
[mapView release];
[super dealloc];
}
@end
And here's the backtrace:
#0 0x01087057 in ___forwarding___ ()
#1 0x01086f22 in __forwarding_prep_0___ ()
#2 0x00eb9f81 in -[MKAnnotationContainerView _annotationViewForSelectionAtPoint:avoidCurrent:] ()
#3 0x00e89c99 in -[MKMapView handleTap:] ()
#4 0x005509c7 in -[UIGestureRecognizer _updateGestureWithEvent:] ()
#5 0x0054c9d6 in -[UIGestureRecognizer _delayedUpdateGesture] ()
#6 0x00552fa5 in _UIGestureRecognizerUpdateObserver ()
#7 0x010f6fbb in __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ ()
#8 0x0108c0e7 in __CFRunLoopDoObservers ()
#9 0x01054bd7 in __CFRunLoopRun ()
#10 0x01054240 in CFRunLoopRunSpecific ()
#11 0x01054161 in CFRunLoopRunInMode ()
#12 0x01a4a268 in GSEventRunModal ()
#13 0x01a4a32d in GSEventRun ()
#14 0x002d642e in UIApplicationMain ()
#15 0x000023bc in main (argc=1, argv=0xbfffefd0) at /Users/chuck/Desktop/learning/flickr/main.m:14
When a crash occurs, there will be a backtrace.
Post it.
Either your program will break in the debugger, and the call stack will be in the debugger UI (or you can type 'bt
With that, the cause of the crash is often quite obvious. Without that, we are left to critique the code.
So, here goes....
Zombie debugging gives: "* -[CFString length]: message sent to deallocated instance 0x5b7b250". Nowhere in my map view or annotation class do I use string length.
Yes, but you likely have strings that your code creates that are passed off to the system frameworks for rendering purposes or to be copied or whatever. And, likely, any one of those tasks requires the string's length.
When debugging zombies, it is helpful to look at the history of retains and releases on a particular object, which is available in the Allocations instrument. Though on a slightly different subject, some of the screenshots and instructions in a post I wrote about find memory accretion may help.
An obvious bug is that you don't retain the title
passed in to your init
method, but you release it in dealloc
. It should be retained; self.title = passTitle;
should do the trick. Note that build and analyze should catch such bugs. Same goes for the Photo
parameter.
Note also that NSString
properties should generally be copy
and not retain
. Copying an immutable string is free and copying a mutable string greatly reduces potential fragility.
精彩评论