Colin Cornaby
Colin Cornaby

Reputation: 746

NSFetchedResultsController with external changes?

I'm reading a CoreData database in a WatchKit extension, and changing the store from the parent iPhone application. I'd like to use NSFetchedResultsController to drive changes to the watch UI, but NSFetchedResultsController in the extension doesn't respond to changes made to the store in the parent application. Is there any way to get the secondary process to respond to changes made in the first process?

Upvotes: 1

Views: 366

Answers (4)

malhal
malhal

Reputation: 30746

You can use Persistent History Tracking via NSPersistentHistory for that, eg

// Create a fetch history request with the last token.
let fetchHistoryRequest = NSPersistentHistoryChangeRequest.fetchHistory(after: lastToken)


// Get a background context.
let backgroundContext = persistentContainer.newBackgroundContext()


// Perform the fetch.
guard let historyResult = await backgroundContext.perform({
    let historyResult = try? backgroundContext.execute(fetchHistoryRequest) as? NSPersistentHistoryResult
    return historyResult?.result
}) else {
    fatalError("Failed to fetch history")
}


// Cast the result as an array of history transactions.
guard let historyTransactions = historyResult as? [NSPersistentHistoryTransaction] else {
    fatalError("Failed to convert history result to history transactions")
}

From: https://developer.apple.com/documentation/coredata/consuming_relevant_store_changes

Upvotes: 0

Alessandro Orrù
Alessandro Orrù

Reputation: 3513

I faced the same problem. My solution applies if you want to update the watch app on main app updates, but it could be easily extended to go both ways.

Note that I'm using a simple extension on NSNotificationCenter in order to be able to post and observe Darwin notification more easily.

1. Post the Darwin notification

In my CoreData store manager, whenever I save the main managed object context, I post a Darwin notification:

notificationCenter.addObserverForName(NSManagedObjectContextDidSaveNotification, object: self.managedObjectContext, queue: NSOperationQueue.mainQueue(), usingBlock: { [weak s = self] notification in

    if let moc = notification.object as? NSManagedObjectContext where moc == s?.managedObjectContext {
        notificationCenter.postDarwinNotificationWithName(IPCNotifications.DidUpdateStoreNotification)
    }

})

2. Listen for the Darwin notification (but only on Watch)

I listen for the same Darwin notification in the same class, but making sure I am on the actual watch extension (in order to avoid to refresh the context that just got updated). I'm not using a framework (must target also iOS 7) so I just added the same CoreDataManager on both main app and watch extension. In order to determine where I am, I use a compile time flag.

#if WATCHAPP
    notificationCenter.addObserverForDarwinNotification(self, selector: "resetContext", name: IPCNotifications.DidUpdateStoreNotification)
#endif

3. Reset context

When the watch extension receives the notification, it resets the MOC context, and sends an internal notification to tell FRCs to update themselves. I'm not sure why, but it wasn't working fine without using a little delay (suggestions are welcome)

func resetContext() {
    self.managedObjectContext?.reset()

    delay(1) {
        NSNotificationCenter.defaultCenter().postNotificationName(Notifications.ForceDataReload, object: self.managedObjectContext?.persistentStoreCoordinator)
    }
}

4. Finally, update the FRCs

In my case, I was embedding a plain FRC in a data structure so I added the observer outside of the FRC scope. Anyway you could easily subclass NSFetchedResultsController and add the following line in its init method (remember to stop observing on dealloc)

NSNotificationCenter.defaultCenter().addObserver(fetchedResultController, selector: "forceDataReload:", name: CoreDataStore.Notifications.ForceDataReload, object: fetchedResultController.managedObjectContext.persistentStoreCoordinator)

and

extension NSFetchedResultsController {
    func forceDataReload(notification: NSNotification) {
        var error : NSError?
        if !self.performFetch(&error) {
            Log.error("Error performing fetch update after forced data reload request: \(error)")
        }

        if let delegate = self.delegate {
            self.delegate?.controllerDidChangeContent?(self)
    }
}

Upvotes: 0

sash
sash

Reputation: 8725

Read this answer to very similar question: https://stackoverflow.com/a/29566287/1757229

Also make sure you set stalenessInterval to 0.

Upvotes: 0

bgilham
bgilham

Reputation: 5939

Some things to try/consider:

Do you have App Groups enabled? If so, is your data store in a location shared between your host app and the extension? If so does deleting the cached data, as referenced here help?

Upvotes: 0

Related Questions