Ios – Memory not being released for MKMapView w/ ARC

iosios6memorymkmapview

I have a custom UIView called ActivityDetailView that I instantiate and then add to a scrollview within a parent view controller. When this custom view is allocated, it takes up about 1mb each time of additional memory and Instruments is showing that the memory is never being released even though the view and the parent view controller each have their dealloc methods being called. I am getting memory warnings and the app is eventually getting killed so I'm obviously doing something wrong.

Updated w/ info about map view being the cause, but I need a fix

Within the custom ActivityDetailView nib file, there is a map view that is zoomed and centered around the users's location. When I removed this map view from the nib so that it doesn't draw on screen, the memory allocation issues went away. However, I obviously need the map view. Why would the map view's data not be released when the map view goes out of scope?

There is only 1 ActivityDetailView and 1 ActivityDetailViewController alive when the view is showing. As soon as I pop the view off the stack, they are no longer living. Doesn't make sense how the memory keeps growing even though the objects are being killed as shown via Instruments. If the parent views are deallocated, why isn't the map view data being deallocated?

What am I doing wrong or what should I check?

Here is the custom view:

@interface ActivityDetailView ()
{
    CLLocation *location;
    __weak id parentViewController;
    int scrollViewX;

    ImageUtility *imageUtility;
}
@end

@implementation ActivityDetailView

-(id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self)
    {
        NSArray *xibViews = [[NSBundle mainBundle] loadNibNamed:@"ActivityDetailView" owner:nil options:nil];
        if ([xibViews count] < 1) return nil;
        ActivityDetailView * xibView = [xibViews objectAtIndex:0];
        [xibView setFrame:frame];
        self = xibView;
    }
    return self;
}

- (id)initWithLocation:(CLLocation *)loc parentController:(id)parent
{
    self = [self initWithFrame:CGRectMake(0, 0, 320, 1000)];
    if (self)
    {
        imageUtility = [ImageUtility sharedManager];

        location = loc;
        parentViewController = parent;
        scrollViewX = 0;

        [self centerMapForActivityLocation];
        [self addPhotoButtonWithImageNamed:@"addActivityPhoto.png" target:parentViewController selector:@selector(addPhotoToActivity:)];
    }
    return self;
}

- (void)dealloc
{
    NSLog(@"ActivityDetailView was dealloced");
}

In the parent view controller:

@interface ActivityDetailViewController ()
{
    ActivityDetailView *detailView;
}
@end

@implementation ActivityDetailViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

    // Some code left out for clarity

    [self setupView];
}

- (void)didReceiveMemoryWarning
{
    NSLog(@"Purging image cache");
    [[ImageUtility sharedManager] purgeCache];
}

- (void)dealloc
{
    // These essentially do nothing to help the problem
    detailView.mapView = nil;
    [detailView removeFromSuperview];
    detailView = nil;
    self.scrollView = nil;

    NSLog(@"ActivityDetailViewController was dealloced");
}

- (void)setupView
{    
    // Add the activity detail view to the scroll view
    detailView = [[ActivityDetailView alloc] initWithLocation:self.activityLocation parentController:self];
    [self.scrollView addSubview:detailView];
    self.scrollView.contentSize = detailView.frame.size;

    // Setup the map view
    detailView.mapView.delegate = self;
    MKPointAnnotation *annotation = [[MKPointAnnotation alloc] init];
    annotation.coordinate = self.activityLocation.coordinate;
    [detailView.mapView addAnnotation:annotation];
    if (self.activity.mapImageName) {
        detailView.mapView.scrollEnabled = YES;
        detailView.mapView.zoomEnabled = YES;
    } else {
        detailView.mapView.scrollEnabled = NO;
        detailView.mapView.zoomEnabled = NO;
    }

    // Add the weather area to the view
    dayView = [[DailyButtonView alloc] initWithFrame:CGRectMake(0, -17, 60, 70)];
    dayView.hidden = YES;
    [detailView.weatherView addSubview:dayView];
}

Here's an image from Instruments showing that the majority of memory is from the nib loading

Instruments screenshot

I don't have anything to cache at the moment. The view is basically a scrollview with a mapview inside of it and some labels and a couple of table views. The table views aren't populated with anything and I'm not loading any images within the view other than the background image for the scrollview, but that should get released when the view does.

Best Solution

As it turns out, there is a known bug with iOS 6 and MKMapView not releasing it's memory correctly. Unfortunately there is no real fix other than trying to force the map view to purge it's cache which doesn't have that great of an impact on releasing memory.

The strange thing is that even when the app is receiving memory warnings, the map view cache is still not being purged properly. Eventually, the app becomes unstable and is killed by the OS.

Apple has been getting very sloppy with their testing lately. This is the second bug with MKMapView that I've come across (the other being mapViewDidFinishLoadingMap: being called early) and both bugs have been really obvious to catch if they had just done some performance and sanity testing.

Here is some code which may help:

- (void)viewDidDisappear:(BOOL)animated
{
    [super viewDidDisappear:animated];

    // This is for a bug in MKMapView for iOS6
    [self purgeMapMemory];
}

// This is for a bug in MKMapView for iOS6
// Try to purge some of the memory being allocated by the map
- (void)purgeMapMemory
{
    // Switching map types causes cache purging, so switch to a different map type
    detailView.mapView.mapType = MKMapTypeStandard;
    [detailView.mapView removeFromSuperview];
    detailView.mapView = nil;
}

The other thing you could do is use one instance of the MKMapView throughout your entire app and that should help minimize how much memory is allocated each time the view is shown since the map view will already be allocated.

Related Question