I am calling a web service that returns an array of annotations in a delegate method that I am adding to my map with the addAnnotations
method for a MKMapView
. Everything goes swimmingly until the delegate method send two arrays in quick succession (usually about 150ms - 500ms) and then I get a EXC_BAD_ACCESS (code=1 address 0x20)
on this line [kMap addAnnotations:tileArray];
- This appears to be a memory issue but i am not really sure what to do how it or how to change my code to address it.
Here is the delegate method
-(void)rebelBaseManager:(RebelBaseManager *)manager didUpdateTileHour:(NSArray *)tileArray boundaryBreak:(NSString *)breakType atTileLevel:(int)callTileLevel {
if (timerStarted == NO) {
[self startTimer];
}
//Check for tileLevel in case multiple calls were made at different tile levels
if (tileLevel == callTileLevel) {
[kMap addAnnotations:tileArray];
[HourInMap addObjectsFromArray:tileArray];
}
}
I also added a method to allow me to animate the removal of annotations which is below in case it makes a difference:
- (void)removeAnnotationsWithFade:(NSArray *)annotations animated:(BOOL)shouldAnimate {
if (!shouldAnimate)
[self removeAnnotations:annotations];
else {
for (HourAnnotation *annotation in annotations) {
MKAnnotationView *annotationView = [self viewForAnnotation:annotation];
[UIView animateWithDuration:2
animations:^{
annotationView.alpha =0;
}completion:^(BOOL finished) {
[self removeAnnotation:annotation];
}];
}
}
--------- ADDITION ---------
Adding in my code from a custom annotation in response to #3 in Rob's answer below.
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}
- (id)initWithHourAnnotation:(HourAnnotation *)hourAnnotation reuseIdentifier:(NSString *)reuseIdentifier {
CGRect myFrame = self.frame;
myFrame.size.width = hourAnnotation.frameSize;
myFrame.size.height = hourAnnotation.frameSize;
self = [super initWithFrame:myFrame];
//When I use this here I seem to get the frame and color of the old annotation displayed
//self = [super initWithAnnotation:velocityAnnotation reuseIdentifier:reuseIdentifier];
if (self)
{
self.layer.cornerRadius = self.frame.size.width / 2;
self.clipsToBounds = YES;
[self setBackgroundColor:[UIColor clearColor]];
NSArray *alphaValue = [[NSArray alloc]initWithArray:[self alphaForTileLevel]];
self.fillColor = [UIColor colorWithHue:hourAnnotation.color saturation:.06 brightness:.23 alpha:[[alphaValue objectAtIndex:hourAnnotation.tileLevel-1]doubleValue]];
self.strokeColor = [UIColor colorWithHue:hourAnnotation.color saturation:.06 brightness:.23 alpha:.35];
self.enabled = NO;
self.canShowCallout = NO;
self.userInteractionEnabled = NO;
}
return self;
}
- (void)drawRect:(CGRect)rect
{
UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:rect];
[self.fillColor set];
[path fill];
[self.strokeColor set];
[path setLineWidth:2.0f];
[path stroke];
}
A couple of thoughts.
You're not showing where you are calling this removeAnnotationsWithFade
. We have to assume that you're removing the appropriate model objects. use instruments to confirm that you're not leaking anywhere.
If the app is crashing when removing with fade, but not crashing without fade, then you might consider refactoring this code. Specifically, your animation code isn't actually removing the old animations until the completion block (i.e. two seconds later). Thus you are holding on to old annotations longer than needed. And if the annotations are coming in fast and furious, you could run into memory problems.
If you, for example, used transitionWithView:mapView
instead, just removing the annotations directly, you might be freeing memory associated with the old annotations more quickly. It's a less elegant animation, but might be "good enough" and minimize your peak memory usage:
// this will immediately remove the annotations, but animate the fading of the transition
[UIView transitionWithView:self.mapView duration:2.0 options:UIViewAnimationOptionTransitionCrossDissolve animations:^{
[self.mapView removeAnnotations:annotations];
} completion:nil];
// presumably remove the annotations from your array, too
Is your viewForAnnotation
dequeuing old annotation views and only instantiating new ones if it couldn't dequeue an old one, or is it always instantiating new ones? Stuff like that might help control peak memory usage, too.