Search code examples
iosswiftcore-datansfetchedresultscontroller

Child NSManagedObjectContext update from parent


I would like to update a child context when it's parent context has been updated.

I have an NSFetchedResultsController using a child context that I then would like to update the UI through it's delegate - I'm not sure if this is totally a correct pattern, here's what I'm doing now:

I create a child context that gets updated from a web service in a class that supports my model. This is a simplified example:

class Messages {

var pmoc: NSManagedObjectContext!
var delegate: MessagesDelegate?

init() {

    let appDel  = UIApplication.sharedApplication().delegate as! AppDelegate
    let moc     = appDel.managedObjectContext

    let pmoc = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
    pmoc.parentContext = moc
    self.pmoc = pmoc
}

func dataToUpdatePrivateContextReceived() {

    // Add things to the private moc
    self.pmoc.performBlock { () -> Void in

        // Create new NSManagedOBject, etc.
        self.savePMOC()
    }
}

func savePMOC() {

    self.pmoc.performBlock { () -> Void in

        do {

            try self.pmoc.save()
            // save main context through an abstraction...
            // Inform any delegate a save has taken place
            self.delegate?.pmocSavedIntoMain()

        } catch let error as NSError {

            print("Save pmoc error :\(error.localizedDescription)")
        }
    }
}
}

protocol MessagesDelegate {
    func pmocSavedIntoMain()
}

Then in some UIViewController I use an NSFetchedResultsController to update a UITableView, I'm trying to use this controller with it's own private context so it's updating doesn't block the UI. Another simplified example:

class ViewController: UIViewController {

var fetchedResultsController: NSFetchedResultsController!
var viewPMOC: NSManagedObjectContext!
let messages = Messages()

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.

    messages.delegate = self

    let appDel  = UIApplication.sharedApplication().delegate as! AppDelegate
    let moc     = appDel.managedObjectContext

    let pmoc = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
    pmoc.parentContext = moc
    self.viewPMOC = pmoc

    let fr = NSFetchRequest(entityName: "MyEntity")
    fr.fetchBatchSize = 20

    let sort = NSSortDescriptor(key: "id", ascending: false)
    fr.sortDescriptors = [sort]

    self.fetchedResultsController = NSFetchedResultsController(fetchRequest: fr,
        managedObjectContext: self.viewPMOC,
        sectionNameKeyPath: nil,
        cacheName: nil)

    self.fetchedResultsController.delegate = self

    do {

        try self.fetchedResultsController.performFetch()

    } catch let error as NSError {

        print("vdl fetch error is: \(error.localizedDescription)")
    }
}
}

extension ViewController: NSFetchedResultsControllerDelegate {

func controllerWillChangeContent(controller: NSFetchedResultsController) {
    // dispatch begin updates on maind thread
}

func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
    // handle update type on main thread
}

func controllerDidChangeContent(controller: NSFetchedResultsController) {
    // dispatch end upds on main thread
}
}

extension ViewController: MessagesDelegate {
func pmocSavedIntoMain() {
    // what can I do here to get the child context to update from
    //parent and thus trigger the fetched results controller to update the view?
}
}

Solution

  • Fetched results controller needs a main context.

    Use this pattern to get rid of the "choppiness".

    RootContext (private queue) - saves to persistent store
    MainContext (main queue) child of RootContext - use for UI (FRC)
    WorkerContext (private queue) - child of MainContext - use for updates & inserts
    

    The your web query is finished, create a worker context and update the data model. When you save, the changes will be pushed up to the main context and your UI should update via the FRC delegate. Save the main and root context to persist.

    Make sure you are using the block methods performBlock and performBlockAndWait throughout when dealing with child contexts.