Justin Wright
Justin Wright

Reputation: 163

CoreData memory leak due to PrivateQueueConcurrencyType

I discovered a memory leak today that manifested itself when calling executeFetchRequest from my main NSManagedObjectContext. I finally discovered that the known offender was from having my NSManagedObjectContext having it’s parent context be assigned to a private managed object context.

Commenting out the line of code that has my main context assign a private parent class and instead directly be pointed to the NSPersistentStoreCoordinator frees my app of all memory leaks.

I was going off of the following article: http://martiancraft.com/blog/2015/03/core-data-stack/ for a design pattern of how to implement CoreData inside my app. I really like the idea of having a private queue be dedicated to just writing disk, and having the main context as the single source for calling when working with the UI.

So my question is, has anybody else run into this problem, and if so do you know a fix instead of just working on one context only to avoid a memory leak?

Below is the section of my CoreDataStack that shows the two context vars.

private lazy var privateManagedObjectContext: NSManagedObjectContext = {
    let moc = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
    moc.persistentStoreCoordinator = self.persistentStoreCoordinator
    return moc
  }()

  lazy var managedObjectContext: NSManagedObjectContext = {
    let managedObjectContext = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)

    // Commenting out  #1 and unCommening #2 prevents the leak from happening
    // However when the reverse happens and we create the private context, a memory leak occurs first thing in the app.

    // #1
    // managedObjectContext.parentContext = self.privateManagedObjectContext

    // And instead replace it with this line
    // #2
    managedObjectContext.persistentStoreCoordinator = self.persistentStoreCoordinator


    return managedObjectContext
  }()

Here is what I have for my save method using the main and private managed object context.

func save() {
    guard managedObjectContext.hasChanges || privateManagedObjectContext.hasChanges else {
      return
    }

    print("Going to save now")

    managedObjectContext.performBlockAndWait() {
      do {
        try self.managedObjectContext.save()
      } catch {
        fatalError("Error saving main managed object context! \(error)")
      }
    }

    privateManagedObjectContext.performBlock() {
      do {
        try self.privateManagedObjectContext.save()
      } catch {
        fatalError("Error saving private managed object context! \(error)")
      }
    }

  }

Upvotes: 5

Views: 1635

Answers (2)

Above The Gods
Above The Gods

Reputation: 175

private lazy var privateManagedObjectContext: NSManagedObjectContext = {
  let moc = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
  moc.parent = managedObjectContext // Set the receiver’s parent context
  return moc
}()

Perhaps this would solve it.

Upvotes: 3

Antonioni
Antonioni

Reputation: 578

There isn't one correct way, but this is how I've implemented saving data in the background while updating the main context on the main thread.

Assign your main MOC (Managed Object Context) as .MainQueueConcurrencyType. Assign your private MOC as .PrivateQueueConcurrencyType and its parentContext as the main MOC.

Save your private MOC on its background thread, then use the performBlock method on your main MOC to save its context, which will save your main MOC's context on the main thread. performBlock makes sure you're on the correct queue. Here is an example below, apologies for it being in Objective-C.

In my setup all of the changes are ALWAYS saved on the private MOC and then propagated to the main MOC. Great book on Core Data, which is actually in Swift at objc.io has many examples and advantages/disadvantages to different scenarios.

// Saving of your private and main context    
[[self class] saveContext:self.yourPrivateContext];

[self.yourPrivateContext.parentContext performBlock:^{
    [[self class] saveContext:self.yourPrivateContext.parentContext];
}];


+(BOOL)saveContext:(NSManagedObjectContext*)context{

    NSError *error = nil;
    if (context != nil) {  
        if ([context hasChanges] && ![context save:&error]) {   
            NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
            abort();
            return NO;    
        } 
    }
    return YES;
}

Upvotes: 0

Related Questions