9

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.

iwasrobbed
  • 46,496
  • 21
  • 150
  • 195
ZeNewb
  • 435
  • 5
  • 17
  • 1
    Not sure if this is the problem (and it shouldn't be). But these two lines of code are wrong detailView = nil;[detailView removeFromSuperview];. You should call removeFromSuperView and after that set it to nil. – Cosmin May 07 '13 at 13:23
  • @Cosmin well I think it can't matter, it wouldn't get to dealloc if it were still added to another view... I am guess our OP was just trying stuff, and left it in. – Grady Player May 07 '13 at 13:27
  • @Cosmin As Grady said, I was just trying anything at that point to see if it made a difference, but it didn't. – ZeNewb May 07 '13 at 13:31
  • @ZeNewb your nib use a lot of images ? – ggrana May 07 '13 at 14:54
  • @ggrana The nib just has one background image, a parent scrollview, a container UIView that has a mapview, a textfield, two tableviews, and some labels. So it's really nothing special. – ZeNewb May 07 '13 at 14:58

3 Answers3

22

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.

iwasrobbed
  • 46,496
  • 21
  • 150
  • 195
  • Great suggestion, re: using one instance of the MKMapView, rather than alloc/dealloc'ing new ones each time. I'm experiencing similar issues... Do we know if this bug has been fixed as of iOS 9+? – Adam W. Dennis Aug 26 '16 at 14:49
  • 1
    @AdamW.Dennis I haven't worked w/ Apple maps in a couple yrs now, so you may want to check on OpenRadar or Apple dev forums to see if you can find more info, e.g. https://openradar.appspot.com/search?query=mkmapview+memory – iwasrobbed Aug 26 '16 at 15:17
0

This is not really the best way to create a custom view from your nib file. What I suggest you do is to add the view that you take from the .nib file as a subview to self. The code should look something like this.

-(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 addSubview:xibView]; // This is where I have changed your code
    }
    return self;
}
Cosmin
  • 2,840
  • 5
  • 32
  • 50
0

You are probably facing a problem with cache.

If you are using a lot of images maybe a good option is to remove this images from the xib and add it using code. Or if you load the images using [UIImage imageNamed:@""]

But use something like that:

NSData* dataImg = [NSData dataWithContentsOfFile:@"yourfile.png"];
UIImage* img = [UIImage imageWithData:dataImg];

To better help I need to know how you start your tables and also the others features.

ggrana
  • 2,335
  • 2
  • 21
  • 31
  • 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. 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. – ZeNewb May 07 '13 at 15:04
  • Nop, the image that you add in your view, using imagenamed or from the xib are cached and sometimes it can crash the app. – ggrana May 07 '13 at 15:05
  • The background image is added in the nib and it's the same background image used throughout the rest of the app, so I don't think this is the issue. None of the other views have allocation issues, only this one does. – ZeNewb May 07 '13 at 15:07