Sharan
Sharan

Reputation: 13

Coredata - Multithreading best way

Parent_Context

Child_context (Set Parent_Context as parent)

Child_context to use in background to add new data or to update existing. Parent_Context to display on UI and to save data persistent store.

Child_context save should not take time as changes are updated only in memory. That is updated to Parent_Context.

Parent_Context save might take time as it writes to store. So we can choose when to save Parent_Context based on the need of the application.

This is how I normally use context when needed in multithreaded environment or to update UI while still accessing data in background.

// Parent or main
_mainQueueContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
_mainQueueContext.persistentStoreCoordinator = self.persistentStoreCoordinator;

// Child or background context
_privateQueueContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[_privateQueueContext setParentContext:self.mainQueueContext]; // Set main threads MOC as parent.

//To Save
    if ([self privateQueueContext].hasChanges) {
        [[self privateQueueContext] performBlockAndWait:^{
            NSError *childError = nil;
            if ([[self privateQueueContext] save:&childError]) {
                [[self mainQueueContext] performBlock:^{
                    NSError *parentError = nil;
                    if (![[self mainQueueContext] save:&parentError]) {
                        DLog(@"Error saving parent, error: %@", [parentError localizedDescription]);
                    }
                }];           
            } else {
                DLog(@"Error saving child, error: %@", [childError localizedDescription]);
            }
        }];
    }

If there is a better way to handle such situation, please share. Thanks.

By saving context in background thread I don't see UI getting freeze. Posting question to know other better ways and learn more on core data.

Upvotes: 0

Views: 631

Answers (1)

oyalhi
oyalhi

Reputation: 3994

I prefer having at least 2 contexts.

The main one associated with the persistent store (with no parent contexts) is privateQueueConcurrencyType so that UI is not affected during the saves to disk.

The second one is viewContext for UI which is the child context for the privateContext.

I usually have another one for background import which is a child context of the UI context and is configured as privateQueueConcurrencyType so it doesn't block the UI. When saved, the UI gets updated, and then changes are saved to the persistent store (when saved recursively).

Also, I create disposable child contexts to the viewContext whenever I will be making changes. I make the changes in the childContext then save recursively. I find this way saved my butt multiple time in multi-user situations.

Below is my setup:

lazy var privateSaveContext: NSManagedObjectContext = {
    let moc = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
    moc.name = "privateSaveContext"
    moc.persistentStoreCoordinator = self.coordinator
    moc.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
    return moc
}()

lazy var viewContext: NSManagedObjectContext = {
    let moc = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
    moc.name = "viewContext"
    moc.parent = self.privateSaveContext
    moc.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
    return moc
}()

lazy var importContext: NSManagedObjectContext = {
    let moc = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
    moc.name = "importContext"
    moc.parent = self.viewContext
    moc.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
    return moc
}()

When saving to disk, I save recursively:

class func save(_ moc: NSManagedObjectContext) {
    moc.performAndWait {
        if moc.hasChanges {
            DLog("Inserted objects count = \(moc.insertedObjects.count)")
            do {
                try moc.save()
                if moc.parent == nil { DLog("SAVED changes to persistent store") }
                DLog("SAVED context '\(moc)'")
            } catch {
                DLog("ERROR saving context '\(moc)' - \(error)")
            }
        } else {
            if moc.parent == nil { DLog("SKIPPED saving changes to persistent store, because there are no changes") }
            DLog("SKIPPED saving context '\(moc)' because there are no changes")
        }
        if let parentContext = moc.parent {
            save(parentContext)
        }
    }
}

p.s. DLog is something I use so that it prints out the function name, date, time etc. You can simply change that to print.

Upvotes: 3

Related Questions