Fred Faust
Fred Faust

Reputation: 6790

Get batch update for NSFetchedResultsController when child contexts are saved

I have two private NSManagedObject contexts that handle updating a Core Data entity asynchronously on a background thread, after those contexts are updated they save into the main context on the main thread. At that point, if there were a lot of updates,my main context and NSFetchedResultsController get slammed with updates. Is there a way to do this in a batch?

I have tried to send notifications, use a delegate, etc. where the data is updated on child contexts and they work as expected, but all fire on a background thread before the main context save action takes place.

I think ideally I would like to detach the delegate while a "big" save is in progress, get notified somehow when it's complete, do an async fetch request and call reloadData on the table when the request has completed.

I have a UITableView and a NSFetchedResultsController using the main context on a UIViewController:

let fetchRequest = NSFetchRequest(entityName: Message)

fetchRequest.predicate  = NSPredicate(format: "groupId == %@", self.groupId)

let idSort          = NSSortDescriptor(key: "id", ascending: true)
fetchRequest.sortDescriptors = [idSort]

self.fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: self.coreDataStack.context, sectionNameKeyPath: nil, cacheName: nil)

do {

try self.fetchedResultsController.performFetch()

} catch {
print("error fetch")
}

self.fetchedResultsController.delegate = self

I then use the delegate provided by NSFetchedResultsController:

func controllerWillChangeContent(controller: NSFetchedResultsController) {

    dispatch_async(dispatch_get_main_queue()) { () -> Void in

        self.tableView?.beginUpdates()
    }
}

func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {

    dispatch_async(dispatch_get_main_queue()) { () -> Void in

        switch type {

        case .Insert:

            self.tableView?.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .Automatic)
            self.newIndexPath = newIndexPath

        case .Delete:
            self.tableView?.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Automatic)

        case .Update:
            return

        case .Move:
            self.tableView?.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Automatic)
            self.tableView?.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .Automatic)

        }
    }
}

func controllerDidChangeContent(controller: NSFetchedResultsController) {

    dispatch_async(dispatch_get_main_queue()) { () -> Void in

        self.tableView?.endUpdates()

        // Scroll the table view
        if self.newIndexPath != nil {
            self.messagesTable?.scrollToRowAtIndexPath(self.newIndexPath!, atScrollPosition: .Bottom, animated: false)
            self.newIndexPath = nil
        }
    }
}

func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) {

    let indexSet = NSIndexSet(index: sectionIndex)

    switch type {

    case .Insert:
        self.tableView!.insertSections(indexSet, withRowAnimation: .Automatic)

    case .Delete:
        self.tableView!.deleteSections(indexSet, withRowAnimation: .Automatic)

    default:
        break

    }
}

Upvotes: 0

Views: 933

Answers (1)

Ron
Ron

Reputation: 1640

Consider changing your context configuration. Instead of creating child contexts of a main queue context, you could create your background context directly on top of the persistent store coordinator. Same goes for the main context. Now when you import your data and save in your background contexts, your data will be written to the sql layer via the psc. In your previous setup, the background contexts would save to their parent, which was the main context. In order to propagate the changes over from the background contexts to the main, listen for the nsmanagedobjectcontextdidsave notification. You can then merge the changes from the background context into the main. This will be less taxing. Depends however a little on the fetch request. Make sure to try to optimize this as much as possible.

Check the doc: https://developer.apple.com/library/ios/documentation/Cocoa/Reference/CoreDataFramework/Classes/NSManagedObjectContext_Class/

Good luck

Upvotes: 1

Related Questions