Changes by mergeChanges(fromContextDidSave:) go unnoticed in ListMonitor #135

Closed
opened 2025-12-29 15:25:15 +01:00 by adam · 7 comments
Owner

Originally created by @manmal on GitHub (Apr 16, 2017).

Hi @JohnEstropia,

First of all, thank you for the great work. CoreData is a beast that needs to be kept in a cage, and CoreStore is a very fine cage :) Alas, I know what I'm talking about. I have sworn to myself to never use CoreData without an idiomatic wrapper again.

I'm currently trying to integrate CoreStore with Ensembles (ensembles.io), which syncs the database with various data sources. The way I would approach this integration is to take the Did-Save-Notification that Ensembles emits and merge it like so:

public func persistentStoreEnsemble(_ ensemble: CDEPersistentStoreEnsemble, didSaveMergeChangesWith notification: Notification) {
    CoreStore.defaultStack.beginAsynchronous { (transaction) in
        transaction.internalContext().mergeChanges(fromContextDidSave: notification)
        transaction.commit({ (saveResult) in
            NSLog(saveResult.coreStoreDumpString) // always returns .success (.hasChanges = false;)
        })
    }
}

That works, insofar as the internalContext then has all the objects in registeredObjects. However, this does not cause updates to be processed by my ListMonitor, and thus the changes do not show up in my UITableView. Restarting the app DOES show the new entries in the table, so the inserts to the DB actually worked (Ensembles writes directly to the store, so that's no surprise; the notification is only a way to integrate changes into the UI-bound contexts).

A potential solution is provided here: http://stackoverflow.com/a/16296365/458603
The idea is to refresh the updated/inserted/deleted objects in the main context such that the changes get noticed by the assigned fetch request.

But I thought, maybe there is a cleaner, more CoreStore-idiomatic way of doing this. It would be really nice to animate the changes, so calling refetch on the ListMonitor is only my fallback option.

Originally created by @manmal on GitHub (Apr 16, 2017). Hi @JohnEstropia, First of all, thank you for the great work. CoreData is a beast that needs to be kept in a cage, and CoreStore is a very fine cage :) Alas, I know what I'm talking about. I have sworn to myself to never use CoreData without an idiomatic wrapper again. I'm currently trying to integrate CoreStore with Ensembles (ensembles.io), which syncs the database with various data sources. The way I would approach this integration is to take the Did-Save-Notification that Ensembles emits and merge it like so: ```swift public func persistentStoreEnsemble(_ ensemble: CDEPersistentStoreEnsemble, didSaveMergeChangesWith notification: Notification) { CoreStore.defaultStack.beginAsynchronous { (transaction) in transaction.internalContext().mergeChanges(fromContextDidSave: notification) transaction.commit({ (saveResult) in NSLog(saveResult.coreStoreDumpString) // always returns .success (.hasChanges = false;) }) } } ``` That works, insofar as the `internalContext` then has all the objects in `registeredObjects`. However, this does not cause updates to be processed by my `ListMonitor`, and thus the changes do not show up in my UITableView. Restarting the app DOES show the new entries in the table, so the inserts to the DB actually worked (Ensembles writes directly to the store, so that's no surprise; the notification is only a way to integrate changes into the UI-bound contexts). A potential solution is provided here: http://stackoverflow.com/a/16296365/458603 The idea is to refresh the updated/inserted/deleted objects in the main context such that the changes get noticed by the assigned fetch request. But I thought, maybe there is a cleaner, more CoreStore-idiomatic way of doing this. It would be really nice to animate the changes, so calling `refetch` on the `ListMonitor` is only my fallback option.
adam closed this issue 2025-12-29 15:25:15 +01:00
Author
Owner

@JohnEstropia commented on GitHub (Apr 16, 2017):

Although I'm not sure if this would work correctly, I think you're on the right track.

I do have some concerns though, is the store (sqlite file) used by Ensembles the same store used by the CoreStore stack?

@JohnEstropia commented on GitHub (Apr 16, 2017): Although I'm not sure if this would work correctly, I think you're on the right track. I do have some concerns though, is the store (sqlite file) used by Ensembles the same store used by the CoreStore stack?
Author
Owner

@manmal commented on GitHub (Apr 16, 2017):

@JohnEstropia Ok, I'll try that and keep you posted.

I do have some concerns though, is the store (sqlite file) used by Ensembles the same store used by the CoreStore stack?

Definitely, yes, the same .momd and the same sqlite File. Not sure whether it would work with a separate database. The great thing about Ensembles is btw that it does not complete a merge if the mainContext has been saved to since the sync started. In that case, the sync has to be restarted. I have to investigate what would happen if I have unsaved changes in the mainContext and merge changes. Probably the changes from the Did-Save-Notification would win.

@manmal commented on GitHub (Apr 16, 2017): @JohnEstropia Ok, I'll try that and keep you posted. > I do have some concerns though, is the store (sqlite file) used by Ensembles the same store used by the CoreStore stack? Definitely, yes, the same .momd and the same sqlite File. Not sure whether it would work with a separate database. The great thing about Ensembles is btw that it does not complete a merge if the mainContext has been saved to since the sync started. In that case, the sync has to be restarted. I have to investigate what would happen if I have unsaved changes in the mainContext and merge changes. Probably the changes from the Did-Save-Notification would win.
Author
Owner

@manmal commented on GitHub (Apr 16, 2017):

This seems to work fine: https://gist.github.com/manmal/1702221a478978668bd9e3fed5e2653e

I'll update the gist in case it needs further changes (should be in production til end of June).

@manmal commented on GitHub (Apr 16, 2017): This seems to work fine: https://gist.github.com/manmal/1702221a478978668bd9e3fed5e2653e I'll update the gist in case it needs further changes (should be in production til end of June).
Author
Owner

@JohnEstropia commented on GitHub (Apr 16, 2017):

@manmal Oh, glad you solved that by yourself :P

Just for reference, CoreStore does the same workaround here:
c0d72799b4/Sources/Internal/NSManagedObjectContext%2BSetup.swift (L118)

Just a heads up though, I think it is ironically safer to use unsafe transactions instead (CoreStore.beginUnsafe()). CoreStore's root context need to merge Ensemble's changes as soon as possible, and asynchronous and synchronous transactions will unnecessarily delay that merge due to their queueing mechanism.

@JohnEstropia commented on GitHub (Apr 16, 2017): @manmal Oh, glad you solved that by yourself :P Just for reference, CoreStore does the same workaround here: https://github.com/JohnEstropia/CoreStore/blob/c0d72799b43d683e0a622769760b20c77f78f9de/Sources/Internal/NSManagedObjectContext%2BSetup.swift#L118 Just a heads up though, I think it is ironically safer to use unsafe transactions instead (`CoreStore.beginUnsafe()`). CoreStore's root context need to merge Ensemble's changes as soon as possible, and asynchronous and synchronous transactions will unnecessarily delay that merge due to their queueing mechanism.
Author
Owner

@manmal commented on GitHub (Apr 16, 2017):

@JohnEstropia Yes, the first heads-up you gave me was already very helpful :)

Just a heads up though, I think it is ironically safer to use unsafe transactions instead (CoreStore.beginUnsafe()). CoreStore's root context need to merge Ensemble's changes as soon as possible, and asynchronous and synchronous transactions will unnecessarily delay that merge due to their queueing mechanism.

But given the scenario that all objects are already known by the main context, would it not be better (smoother UI) if those were inserted on the background thread? Or does this not really matter because the eventual background-to-main-merging would do the very same insertions/updates on the main thread anyway?

@manmal commented on GitHub (Apr 16, 2017): @JohnEstropia Yes, the first heads-up you gave me was already very helpful :) > Just a heads up though, I think it is ironically safer to use unsafe transactions instead (CoreStore.beginUnsafe()). CoreStore's root context need to merge Ensemble's changes as soon as possible, and asynchronous and synchronous transactions will unnecessarily delay that merge due to their queueing mechanism. But given the scenario that all objects are already known by the main context, would it not be better (smoother UI) if those were inserted on the background thread? Or does this not really matter because the eventual background-to-main-merging would do the very same insertions/updates on the main thread anyway?
Author
Owner

@JohnEstropia commented on GitHub (Apr 16, 2017):

CoreStore is not designed to accept changes from the main context. All transactions in CoreStore (including unsafe transactions) are background contexts.

@JohnEstropia commented on GitHub (Apr 16, 2017): CoreStore is not designed to accept changes from the main context. All transactions in CoreStore (including unsafe transactions) are background contexts.
Author
Owner

@manmal commented on GitHub (Apr 16, 2017):

Ok great! I updated my code with beginUnsafe - it's all-main-thread now.

https://gist.github.com/manmal/1702221a478978668bd9e3fed5e2653e

@manmal commented on GitHub (Apr 16, 2017): Ok great! I updated my code with `beginUnsafe` - it's all-main-thread now. https://gist.github.com/manmal/1702221a478978668bd9e3fed5e2653e
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/CoreStore#135