iOS Share Extension + NSFetchedResultsController not returning results

I am sharing my CoreData model between my iOS main app target and a new Share Extension target like this post: 

This is working well for the most part except for one thing.  NSFetchedResultsController is not returning results when called from the Shared Extension. What is strange though is that if I do a plain NSFetchRequest in my Share Extension, I do get CoreData results returned that were originally saved from the main app...so I think Container setup as well as model must be being shared correctly via AppContainer. NSFetchedResultsControllerDelegate controllerDidChangeContent is never called.

Any ideas or suggestions?

import UIKit
import MobileCoreServices

class ShareViewController: UIViewController {

    private(set) lazy var resultsController: NSFetchedResultsController<Person> = createFetchedResultsController()

    override func viewDidLoad() {
        super.viewDidLoad()

        let fetchRequest = NSFetchRequest<Person>(entityName: "Person")
        fetchRequest.sortDescriptors = [NSSortDescriptor(key: "date", ascending: false)]

        do {
                /// this works!
            let persons = try CoreDataManager.shared.managedObjectContext.fetch(fetchRequest)
            print("Got \(persons.count) Persons")
        } catch {
            print("Fetch failed")
        }

        activateResultsController()
    }

    func createFetchedResultsController() -> NSFetchedResultsController<Person> {
        CoreDataManager.shared.container.viewContext.stalenessInterval = 0
        CoreDataManager.shared.container.viewContext.refreshAllObjects()
        CoreDataManager.shared.container.viewContext.stalenessInterval = -1

        let fetchRequest = NSFetchRequest<Person>(entityName: "Person")
        fetchRequest.sortDescriptors = [NSSortDescriptor(key: "date", ascending: false)]

        ////managedObjectContext: CoreDataManager.shared.managedObjectContext,
        let controller = NSFetchedResultsController(
                fetchRequest: fetchRequest,
                managedObjectContext: CoreDataManager.shared.managedObjectContext,
                sectionNameKeyPath: nil,
                cacheName: nil
        )
        controller.delegate = self

        return controller
    }

    private func activateResultsController() {
        do {
            try resultsController.performFetch()
        } catch {
            fatalError("Failed to fetch entities: \(error)")
        }
    }
}


// MARK: - Results Controller Delegate

extension ShareViewController: NSFetchedResultsControllerDelegate {

    func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {

        guard let sections = resultsController.sections else {
            return
        }

        let section = sections[0]
        let rows = section.numberOfObjects
        print("rows=\(rows)")
    }
}


import UIKit
import CoreData

class CoreDataManager {

    static let shared = CoreDataManager()

    internal var container: NSPersistentContainer

    var managedObjectContext: NSManagedObjectContext {
        container.viewContext
    }

    init() {
        container = NSPersistentContainer(name: Constants.name)

        guard let storeDirectory = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first else {
            // We'll throw a fatalError() because we can't really proceed without storeDirectory
            fatalError(file: "Could not find .applicationSupportDirectory - exiting")
        }

        let storeURL = storeURL(for: "group.mygroup.testshareextensioncoredata", databaseName: "\(Constants.name)")
        let storeDescription = NSPersistentStoreDescription(url: storeURL)
        container.persistentStoreDescriptions = [storeDescription]

        container.loadPersistentStores(completionHandler: { storeDescription, error in
            if let error = error as NSError? {
                // We'll throw a fatalError() because we can't really proceed without loading the PersistentStore
                fatalError("loadPersistentStore failed \(error), \(error.userInfo)")
            }
        })
    }

    // MARK: - Core Data Saving support

    func saveContext() {
        managedObjectContext.performAndWait {
            if managedObjectContext.hasChanges {
                do {
                    try managedObjectContext.save()
                } catch {
                }
            }
        }
    }


    /// Returns a URL for the given app group and database pointing to the sqlite database.
    func storeURL(for appGroup: String, databaseName: String) -> URL {
        guard let fileContainer = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroup) else {
            fatalError("Shared file container could not be created.")
        }

        return fileContainer.appendingPathComponent("\(databaseName).sqlite")
    }
}


internal extension CoreDataManager {
    enum Constants {
        static let name = "ShareExtensionCoreDataTest"
    }
}

Replies

Changes made in one process aren't automatically propagated to other processes that share the same core data file. NSPersistentHistoryTracking was introduced to address this. See https://developer.apple.com/videos/play/wwdc2019/230

Hi deeje,

Thanks for your reply.

In principle I'd agree - however as I mentioned, a normal NSFetchRequest works

So from this ViewController in the share extension, the NSFetchRequest returns the same core data results.

override func viewDidLoad() {
    super.viewDidLoad()

    let fetchRequest = NSFetchRequest<Person>(entityName: "Person")
    fetchRequest.sortDescriptors = [NSSortDescriptor(key: "date", ascending: false)]

    do {
            /// this works!
        let persons = try CoreDataManager.shared.managedObjectContext.fetch(fetchRequest)
        print("Got \(persons.count) Persons")
    } catch {
        print("Fetch failed")
    }

    activateResultsController()
}

Its just NSFetchedResultsController with the same NSFetchRequest not returning results.

It doesn't make sense to me why a vanilla NSFetchRequest would work however a NSFetchRequest inside NSFetchedResultsController would no work?

Thx

Note that NSFetchRequest will always read from disk, which explains why it's working for you. NSFetchedResultsController uses a cache where possible (as the documentation explains). You can purge the cache if you wish: https://developer.apple.com/documentation/coredata/nsfetchedresultscontroller?language=objc