NSFetchedResultsController - persisted data only

Hi all -


May be a simple question but I'm not finding a solution here - wood for the trees and all that


Any ideas how I can implement NSFetchedResultsController to only work with data saved to Core Data?


I can set includesPendingChanges on the underlying FetchRequest but this only has an effect on the performFetch operation. When I create an instance of the NSManagedObject subclass it seems to add it to the data layer in a pending state and therefore the NSFetchedResultsController picks it up for displaying in the UITableView.


Hope that makes sense…

Accepted Reply

So I double checked some stuff and the short version: NSFetchedResultsController seems to be rigged up to effectively process the "objects did change" notifications from the context, the notifications you don't want if you only want to display the saved changes. The includesPendingChanges flag doesn't seem to get used other than for the initial results, like you reported. (It would be nice if the docs were more explicit about that. 😟 )


So if you don't want pending changes displayed, performing the changes in another context is going to be the simplest way to prevent the fetched results controller displaying pending results. You could mess around with the delegate method so that you batched up all of the change notifications until you got a 'did save' notification from the context, but that would take more memory and effort than just doing all of your changes in the other context--you'll get all of the right change notifications when the other context saves and the changes get imported into the fetched results controller's context. (It should also work if you have the fetched results controller and the "working" context connected separately to the persistent store coordinator. But I forget if you have to do anything special to get change notifications from the parent context/coordinator.)

Replies

A few things...

  1. Stop and make sure you don't have any code performing an automatic save of your context.
  2. includesPendingChanges defaults to true. You want it set to false.
  3. If all else fails, you need to make all of your changes in a nested context.

Thanks for the info... I've double checked and we're def displaying non-saved core data in the UITableView.


    fileprivate let managedObjectContext = (UIApplication.shared.delegate as! AppDelegate).managedObjectContext /

    lazy var fetchedResultsController: NSFetchedResultsController = { () -> NSFetchedResultsController<NSFetchRequestResult> in

        let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Connection")
        fetchRequest.includesPendingChanges = false
     
        let sortDescriptor = NSSortDescriptor(key: "indexPathRow", ascending: true)
        fetchRequest.sortDescriptors = [sortDescriptor]
     
        let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: self.managedObjectContext, sectionNameKeyPath: nil, cacheName: nil)

        fetchedResultsController.delegate = self
     
        return fetchedResultsController
    }()


These are the properties I'm using for my NSFetchedResultsController. The performFetch happens in viewDidLoad and these are my delegate methods


   func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {tableView.beginUpdates()}
    func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {tableView.endUpdates()}
    func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, for type: NSFetchedResultsChangeType) {}
    func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, sectionIndexTitleForSectionName sectionName: String) -> String? {return ""}
   
    func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
        switch (type) {
        case .insert: if var indexPath = newIndexPath {
            indexPath.section = indexPath.section + 1
            tableView.insertRows(at: [indexPath], with: .fade)
            }
        case .delete: if var indexPath = indexPath {
            indexPath.section = indexPath.section + 1
            tableView.deleteRows(at: [indexPath], with: .fade)
            }
        case .update: if var indexPath = indexPath {
            indexPath.section = indexPath.section + 1
            tableView.reloadRows(at: [indexPath], with: .none)
            }
        case .move:
            if var indexPath = indexPath {
                indexPath.section = indexPath.section + 1
                tableView.deleteRows(at: [indexPath], with: .fade)
            }
            if var newIndexPath = newIndexPath {
                newIndexPath.section = newIndexPath.section + 1
                tableView.insertRows(at: [newIndexPath], with: .fade)
            }
        }
    }

So I double checked some stuff and the short version: NSFetchedResultsController seems to be rigged up to effectively process the "objects did change" notifications from the context, the notifications you don't want if you only want to display the saved changes. The includesPendingChanges flag doesn't seem to get used other than for the initial results, like you reported. (It would be nice if the docs were more explicit about that. 😟 )


So if you don't want pending changes displayed, performing the changes in another context is going to be the simplest way to prevent the fetched results controller displaying pending results. You could mess around with the delegate method so that you batched up all of the change notifications until you got a 'did save' notification from the context, but that would take more memory and effort than just doing all of your changes in the other context--you'll get all of the right change notifications when the other context saves and the changes get imported into the fetched results controller's context. (It should also work if you have the fetched results controller and the "working" context connected separately to the persistent store coordinator. But I forget if you have to do anything special to get change notifications from the parent context/coordinator.)

Finally got back to this - got sidetracked on other work.

Working on a different context as you suggested worked like a charm. Many thanks for the pointers both here and the other thread - much appreciated.