Reputation: 163
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
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
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