1
votes

I've been reading some links and posts regarding using parent and child NSManagedObjectContext in Core Data, but the example scenarios I've found deal with simply editing the attributes of a certain object in the parent context, and then pushing changes to the parent context.

The scenario I need to handle is a bit complex and I'm not sure how to manage it:

I have a set of managed objects in the default context (the one that is provided in AppDelegate in the main queue). My app periodically call web services to check if there are updates for those objects. I want to do perform these updates in a separated thread to avoid blocking de UI, so, when it's time to call de services to ask for updates, I do this:

    let bundle = NSBundle.mainBundle()
    let modelURL = bundle.URLForResource("MyApp", withExtension: "momd")
    let model = NSManagedObjectModel(contentsOfURL: modelURL!)!

    let psc = NSPersistentStoreCoordinator(managedObjectModel: model)

    let privateContext = NSManagedObjectContext(concurrencyType: NSManagedObjectContextConcurrencyType.PrivateQueueConcurrencyType)
    privateContext.persistentStoreCoordinator = psc

    let documentsURL = CoreDataHelper.applicationDocumentsDirectory()
    let storeURL = documentsURL.URLByAppendingPathComponent("MyApp.sqlite")

    let options = [NSMigratePersistentStoresAutomaticallyOption: true]

    var error: NSError? = nil
    let store: NSPersistentStore? = psc.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: storeURL, options: options, error: &error)

That is, I create a new context in a private queue and with its own new persistent store coordinator and persistent store.

Once I have my private context, I request the updates to services:

[privateContext performBlockAndWait: ^{
        // Call services
        // Create new objects in this private context with
        // the data in services responses
    }];

Everything ok at this point. I have an "old" set of objects in my main context, and a "new" set of objects in the private context. I need to replace the set of objects of the main context with the new set of objects in the private one. The issues here are:

  1. Some of the objects that are in main context could not exist anymore in the private context, so I'll need to delete them.
  2. There could be new objects in the private context that don't exist in the main context, I'll need to add them.
  3. There could be objects in the private context that also are in the main one, but need to update some of their attributes.

I don't know if parent/child contexts can handle this kind of updates, or they are only appropriate for letting users to edit certain objects. Here it is not the user who edits the objects, so I need to avoid blocking the UI, and furthermore I'm not just modifying objects information, but deleting and adding new objects if needed.

I have some questions regarding parent/child contexts:

  1. Can a child context be in a private queue and the parent context in the main queue?
  2. I've read somewhere something about setting a merge policy, but I didn't find examples of its use, maybe is not necessary to set a merge policy when usen parent/child contexts? When would they be set? Where can I find an example or tutorial?
  3. If I set my private context as child of the main context, and I save the child private context, will the objects in private context "replace" the objects in the main context as I want? (including deleting the objects that are no longer present in the private context and the new ones)... I mean... does the whole child context replace the whole parent context?
  4. Would it be better to save the private context without being a child of the main one, and then clearing and refetching all the new data in the main context?

I need help to understand how this works and which is the best way to manage my scenario.

Thanks in advance.

List item

1
You should register for context save notifications on the main context. then perform all the work using the private context on the background thread. That is, fetch the remote data, perform the search, delete, update and adds on this thread using the private context - remember you have access to the same data the main context is seeing. Once complete call moc.save on the private context which will trigger a save notification to the main context, then call the main context merge API. Do this latter call from the main thread, the notification will be posted on the background thread.Duncan Groenewald
@DuncanGroenewald what does "call the main context merge API" mean?AppsDev

1 Answers

0
votes

Mmm, you should read some of the Core Data/iCloud documents and watch some of the WWDC video's to get an understanding of the basics. Basically when you do updates on a background context (or any context other than the main one) and save then you need to call an API from the main context to merge these changes into the main context.

Here are some code snippets:

// Set the moc here because its defined as Lazy it may be initialised to nil already by
                // something!
                let newMoc = NSManagedObjectContext()
                newMoc.persistentStoreCoordinator = persistentStoreCoordinator
                // Set the MergePolicy to prioritise external inputs
                let mergePolicy = NSMergePolicy(mergeType:NSMergePolicyType.MergeByPropertyStoreTrumpMergePolicyType )
                newMoc.mergePolicy = mergePolicy
                managedObjectContext = newMoc

...

/* Loads the required seed data */
    // Usually called on a background thread and therefor we need to process the DidSave notification
    // to merge the changed with the main context so the UI gets updated
    func loadSeedData() {
        //FLOG(" called");

        let bgContext:NSManagedObjectContext = NSManagedObjectContext(concurrencyType: NSManagedObjectContextConcurrencyType.ConfinementConcurrencyType)

        // Register for saves in order to merge any data from background threads
        NSNotificationCenter.defaultCenter().addObserver(self, selector:"storesDidSave:", name: NSManagedObjectContextDidSaveNotification, object:bgContext)

        while (persistentStoreCoordinator == nil) {
            //FLOG(@" persistentStoreCoordinator = nil, waiting 5 seconds to try again...");
            sleep(5);
        }

        bgContext.persistentStoreCoordinator = persistentStoreCoordinator


        insertNewWalletDetails(bgContext, name:"Info")

...

bgContext.processPendingChanges()

        do {

            try bgContext.save()

            //FLOG(" Seed data loaded")

        } catch {
            //FLOG("  Unresolved error \(error), \(error?.userInfo)")
        }

...

// We only care if the one we have open is changing
    func registerForStoreChanges(storeCoordinator: NSPersistentStoreCoordinator) {

        //FLOG("registerForStoreChanges called")
        let nc = NSNotificationCenter.defaultCenter()

        nc.addObserver(self, selector: "storesWillChange:", name: NSPersistentStoreCoordinatorStoresWillChangeNotification, object: storeCoordinator)

        nc.addObserver(self, selector: "storesDidChange:", name: NSPersistentStoreCoordinatorStoresDidChangeNotification, object: storeCoordinator)

        nc.addObserver(self, selector: "storesDidImport:", name: NSPersistentStoreDidImportUbiquitousContentChangesNotification, object: storeCoordinator)

    }

...

// NB - this may be called from a background thread so make sure we run on the main thread !!
    // This is when transaction logs are loaded
    func storesDidSave(notification: NSNotification!) {

        // Ignore any notifications from the main thread because we only need to merge data
        // loaded from other threads.
        if (NSThread.isMainThread()) {
            //FLOG(" main thread saved context")
            return
        }

        NSOperationQueue.mainQueue().addOperationWithBlock {
            //FLOG("storesDidSave ")
            // Set this so that after the timer goes off we perform a save
            // - without this the deletes don't appear to trigger the fetchedResultsController delegate methods !
            self.import_or_save = true

            self.createTimer()
            if let moc = self.managedObjectContext {
                moc.mergeChangesFromContextDidSaveNotification(notification)
            }

        }
    }

For a working sample app and additional explanations look here enter link description here