Search code examples
objective-ccore-datamemory-leaksdealloc

Memory not releasing when calling dealloc on NSFetchedResultsController


I am trying to debug a memory leak within my code (2.5mb) and it seems to be pointing to _prepareResultsFromResultSet within NSFetchedResultsController. I have 2 viewControllers (A & B), viewController B is pushed on to the navigationController from viewController A.

In B I am performing an NSFetchRequest using NSFetchedResultsController as follows:

.h

@property (nonatomic, retain) NSFetchedResultsController *fetchedResultsController;
@property (nonatomic, retain) NSString *sMapSlug;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil andMapSlug: (NSString *)slug;

.m

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil andMapSlug: (NSString *)slug
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // Custom initialization
        self.sMapSlug = [NSString stringWithString:slug];
    }
    return self;
}
- (NSFetchedResultsController *)fetchedResultsController {

    if (_fetchedResultsController != nil)
        return _fetchedResultsController;

    Singleton *singleton = [Singleton sharedSingleton];

    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"DescriptionEntity" inManagedObjectContext:[singleton managedObjectContext]];
    [fetchRequest setEntity:entity];

    NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey:@"map.sName" ascending:NO];
    [fetchRequest setSortDescriptors:[NSArray arrayWithObject:sort]];
    NSPredicate *myPredicate = [NSPredicate predicateWithFormat:@"map.sSlug LIKE %@", self.sMapSlug];
    [fetchRequest setFetchBatchSize:8];
    [fetchRequest setPredicate:myPredicate];

    // Finally check the results
    NSError *error;
    NSArray *fetchedObjects = [[singleton managedObjectContext] executeFetchRequest:fetchRequest error:&error];
    for (DescriptionEntity *desc in fetchedObjects)
    {
        NSLog(@"Maps present in database: %@", desc.map.sName);
    }        

    NSFetchedResultsController *theFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:[singleton managedObjectContext] sectionNameKeyPath:nil cacheName:@"Test1"];
    self.fetchedResultsController = theFetchedResultsController;
    self.fetchedResultsController.delegate = self;
    //[self.fetchedResultsController performFetch:NULL];
    [theFetchedResultsController release];
    [fetchRequest release];
    [sort release];

    return _fetchedResultsController;
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view from its nib.
    [self.fetchedResultsController fetchRequest];
}

Everything is loaded in fine using the fetchRequest and I can access my data if need be, however I have taken this out for now so I know none of the data from the NSFetchedRequestController is being used. When I go back on the navigation stack the dealloc for controller B is called, and I perform the following:

- (void)dealloc {
    self.fetchedResultsController.delegate = nil;
    [_fetchedResultsController release];
    [sMapSlug release];

    [super dealloc];
}

When I take heap shots of this within Instruments I see the following data stay after the dealloc is called:

enter image description here

If I go back in to controller B again, another set of this memory is added and never removed. However, if I do this a 3rd time the amount does not increment. I am presuming that there is some sort of caching going on, can anyone tell me how to remove this data or how to correctly deallocate an NSFetchedResultsController.

If you need any more information feel free to ask.


Solution

  • Usually you should not worry about memory management in Core Data. Yes, under the hood a caching mechanism is involved.

    Anyway, there are two different methods to "dismiss" the memory.

    The first is refreshObject:mergeChanges: method. Passing to it NO, it allows to prune the object graph. In other words, it throws away any changed data that has not been saved to the store.

    Override willTurnIntoFault and didTurnIntoFault to see it in action.

    The other is to call reset on a managed context. Obviosly, this removes all the objects the context contains.

    Hope that helps.