Originally created by @Koshub on GitHub (May 4, 2016).
Although it is only my opinion I think it is common problem I described in (old issue) when immediately loading data after update is not loaded updated values. That issue is not so correct because I had not seen the CoreStore internal design.
As a User I would like to expect that after the transaction has finished with commitAndWait method the UI context has already been updated and all its entities are updated from root context. Currently UI context is a child context by design, but it is not updated immediately after commitAndWait finished. There is handling NSManagedObjectContextDidSaveNotification and fetching with UI context is not waiting for NSManagedObjectContextDidSaveNotification processing finish. Also the NSManagedObjectContextObjectsDidChangeNotification is not handled but I think UI context as child of root context should be updated with this notification too.
I understand that current design is selected for performance reasons and currently UI is not waiting UI context mergings from root. Unfortunately I have to update every object I fetched with refreshAndMerge to show in UI. It is very easy to make a bug if I forget put refreshAndMerge somewhere in my code.
Is it possible to make commitAndWait method more predictable to allow user to choose if he prefers to merge transaction with UI context or not?
Thank you.
Originally created by @Koshub on GitHub (May 4, 2016).
Although it is only my opinion I think it is common problem I described in [(old issue)](https://github.com/JohnEstropia/CoreStore/issues/62) when immediately loading data after update is not loaded updated values. That issue is not so correct because I had not seen the CoreStore internal design.
As a User I would like to expect that after the transaction has finished with `commitAndWait` method the UI context has already been updated and all its entities are updated from root context. Currently UI context is a child context by design, but it is not updated immediately after `commitAndWait` finished. There is handling `NSManagedObjectContextDidSaveNotification` and fetching with UI context is not waiting for `NSManagedObjectContextDidSaveNotification` processing finish. Also the `NSManagedObjectContextObjectsDidChangeNotification` is not handled but I think UI context as child of root context should be updated with this notification too.
I understand that current design is selected for performance reasons and currently UI is not waiting UI context mergings from root. Unfortunately I have to update every object I fetched with `refreshAndMerge` to show in UI. It is very easy to make a bug if I forget put `refreshAndMerge` somewhere in my code.
Is it possible to make `commitAndWait` method more predictable to allow user to choose if he prefers to merge transaction with UI context or not?
Thank you.
Let me point out again that this is a only a problem when using synchronous transactions.
This is caused by the asynchronous context?.performBlock {...} handling in NSManagedObjectContextDidSaveNotification. I've tried the following workarounds below but unfortunately all of them have huge drawbacks.
1. Try using performBlockAndWait in the NSManagedObjectContextDidSaveNotification handler
This causes a deadlock when the UI does a fetch while an asynchronous transaction is saving. It's very easy to reproduce in one of our apps that downloads a user's history of chat messages continuously after reinstall.
2. Try calling refreshObject(_:mergeChanges:) on all the main context's registeredObjects inside commitAndWait()
This solves your problem, but the tradeoff looks bigger than the convenience it offers:
Since the main context does not know which objects are being updated (the updatedObjects property is empty at this point), we can only call refreshObject(_:mergeChanges:) on all the objects. If you have thousands of objects in memory, all of them will be refreshed and merged. We can try to refresh objects as faults which would be significantly cheaper, but that would cause transient property values to break and recompute.
NSFetchedResultsControllers (and ListMonitors and ObjectMonitors) get notified twice of the change, one from the NSManagedObjectContextDidSaveNotification merge and one from the explicit refresh.
3. Track the transaction's updatedObjects and call those objects' refreshObject(_:mergeChanges:) inside commitAndWait()
This is better than (2), but
NSFetchedResultsControllers still get notified twice for each change.
Since we selectively update objects, you may introduce conflicts in relationship values. (this is no different when calling object.refreshAndMerge() though)
I'm stuck here so far. Personally, I do not encourage synchronous transactions in the first place. I always recommend using ListMonitors and ObjectMonitors and pass those around instead of NSManagedObject instances themselves, then handle all changes through the observer delegates.
I do understand that this use case is sometimes unavoidable, so CoreStore exposes several refresh***() methods. So far I'm thinking of changing commitAndWait() to commitAndWait(refreshObjectsImmediately:) and with default value of refreshObjectsImmediately = false. If you have any other ideas, let me know.
@JohnEstropia commented on GitHub (May 6, 2016):
Let me point out again that _this is a only a problem when using synchronous transactions._
This is caused by the asynchronous `context?.performBlock {...}` handling in `NSManagedObjectContextDidSaveNotification`. I've tried the following workarounds below but unfortunately all of them have huge drawbacks.
**1. Try using `performBlockAndWait` in the `NSManagedObjectContextDidSaveNotification` handler**
This causes a deadlock when the UI does a fetch while an asynchronous transaction is saving. It's very easy to reproduce in one of our apps that downloads a user's history of chat messages continuously after reinstall.
**2. Try calling `refreshObject(_:mergeChanges:)` on all the main context's `registeredObjects` inside `commitAndWait()`**
This solves your problem, but the tradeoff looks bigger than the convenience it offers:
- Since the main context does not know which objects are being updated (the `updatedObjects` property is empty at this point), we can only call `refreshObject(_:mergeChanges:)` on all the objects. If you have thousands of objects in memory, all of them will be refreshed _and merged_. We can try to refresh objects as faults which would be significantly cheaper, but that would cause transient property values to break and recompute.
- `NSFetchedResultsController`s (and `ListMonitor`s and `ObjectMonitor`s) get notified twice of the change, one from the `NSManagedObjectContextDidSaveNotification` merge and one from the explicit refresh.
**3. Track the transaction's `updatedObjects` and call those objects' `refreshObject(_:mergeChanges:)` inside `commitAndWait()`**
This is better than (2), but
- `NSFetchedResultsController`s still get notified twice for each change.
- Since we selectively update objects, you may introduce conflicts in relationship values. (this is no different when calling `object.refreshAndMerge()` though)
I'm stuck here so far. Personally, I do not encourage synchronous transactions in the first place. I always recommend using `ListMonitor`s and `ObjectMonitor`s and pass those around instead of `NSManagedObject` instances themselves, then handle all changes through the observer delegates.
I do understand that this use case is sometimes unavoidable, so CoreStore exposes several `refresh***()` methods. So far I'm thinking of changing `commitAndWait()` to `commitAndWait(refreshObjectsImmediately:)` and with default value of `refreshObjectsImmediately = false`. If you have any other ideas, let me know.
Well I think that all existed stuff should be the same to make backward compatibility with avoiding NSFetchedResultsControllers and other listeners get notified twice. One needs to extend functionality probably based on the next ideas.
Every child context including UI context (because it is a child too) should have an ability to be configured to handle updates from his parent with NSManagedObjectContextDidSaveNotification notification with only performBlock call ( without performBlockAndWait). It gives an ability to make child be up to date without locks. Currently only UI context implements this thing but always and is not configured to turn off this functionality. Ability to configure child updates will gives more flexibility.
The next thing is to give parent context links with all its available child contexts. Then by improving saveSynchronously() (for example will be saveSynchronously(mergeChilds mergeChilds: Bool = false)) method of NSManagedObjectContext extension we could get all child contexts and after(or before) try self.save() start waiting for finishing all mergeChangesFromContextDidSaveNotification of child contexts which were configured to handle updates from their parent. After finish all blocks of child updates we continue saveSynchronously() to do other existence work (save parent if presented, etc).
Probably it could be hard to make some synchronisation of notifications queue ( of what save what notification sends) or/and hard to implement correctly, but I think it could make sense.
@Koshub commented on GitHub (May 7, 2016):
Well I think that all existed stuff should be the same to make backward compatibility with avoiding `NSFetchedResultsControllers` and other listeners get notified twice. One needs to extend functionality probably based on the next ideas.
Every child context including UI context (because it is a child too) should have an ability to be configured to handle updates from his parent with `NSManagedObjectContextDidSaveNotification` notification with only `performBlock` call ( without `performBlockAndWait`). It gives an ability to make child be up to date without locks. Currently only UI context implements this thing but always and is not configured to turn off this functionality. Ability to configure child updates will gives more flexibility.
The next thing is to give parent context links with all its available child contexts. Then by improving `saveSynchronously()` (for example will be `saveSynchronously(mergeChilds mergeChilds: Bool = false)`) method of `NSManagedObjectContext` extension we could get all child contexts and after(or before) `try self.save()` start waiting for finishing all `mergeChangesFromContextDidSaveNotification` of child contexts which were configured to handle updates from their parent. After finish all blocks of child updates we continue `saveSynchronously()` to do other existence work (save parent if presented, etc).
Probably it could be hard to make some synchronisation of notifications queue ( of what `save` what notification sends) or/and hard to implement correctly, but I think it could make sense.
Every child context including UI context (because it is a child too) should have an ability to be configured to handle updates from his parent
This is unnecessary for transaction contexts because CoreStore guarantees that no transactions overlap (with the exception of unsafe transactions, which is why they're "unsafe"). This means that all transactions start with fresh and correct data state. And since the main context is read-only, you are guaranteed that no other transaction is meddling with the data. Check the README explanation of how the root context propagate data to the UI context and the transaction contexts: the UI context gets data from "merges" and the transaction contexts get data from the context-nesting mechanism.
The next thing is to give parent context links with all its available child contexts...
Your idea was on the right track. Maybe we can find a way to send an isSynchronous flag to NSManagedObjectContextDidSaveNotification so we can decide wether to use performBlock() or performBlockAndWait(). Something like
context.isSaveSynchronous=true// set with obj_setAssoccontext.save()context.isSaveSynchronous=nil
I'll try it out later when I have access to my PC
@JohnEstropia commented on GitHub (May 7, 2016):
Thanks for the ideas!
> Every child context including UI context (because it is a child too) should have an ability to be configured to handle updates from his parent
This is unnecessary for transaction contexts because CoreStore guarantees that no transactions overlap (with the exception of unsafe transactions, which is why they're "unsafe"). This means that all transactions start with fresh and correct data state. And since the main context is read-only, you are guaranteed that no other transaction is meddling with the data. Check the README explanation of how the root context propagate data to the UI context and the transaction contexts: the UI context gets data from "merges" and the transaction contexts get data from the context-nesting mechanism.
> The next thing is to give parent context links with all its available child contexts...
Your idea was on the right track. Maybe we can find a way to send an `isSynchronous` flag to `NSManagedObjectContextDidSaveNotification` so we can decide wether to use `performBlock()` or `performBlockAndWait()`. Something like
``` swift
context.isSaveSynchronous = true // set with obj_setAssoc
context.save()
context.isSaveSynchronous = nil
```
I'll try it out later when I have access to my PC
Oh, no change needed on the caller side. Just update CoreStore and your current code should work as is
@JohnEstropia commented on GitHub (May 7, 2016):
Oh, no change needed on the caller side. Just update CoreStore and your current code should work as is
Great. Thank you for fix. Unfortunately I can't check it on real code, because I moved to plain CoreData management before I posted this issue. I will definitely check a bit later. According to changes in code I prefer blocking in sync function saveSynchronously to show that the function is real sync, but performBlockAndWait should work I think. Thank you for responses. Will let you know in any case.
@Koshub commented on GitHub (May 7, 2016):
Great. Thank you for fix. Unfortunately I can't check it on real code, because I moved to plain CoreData management before I posted this issue. I will definitely check a bit later. According to changes in code I prefer blocking in sync function `saveSynchronously` to show that the function is real sync, but `performBlockAndWait` should work I think. Thank you for responses. Will let you know in any case.
You can confirm the fixed behavior with the CoreStoreDemo app.
Open the TransactionsDemoViewController.swift and change the refreshButtonTapped(_:) method to
Prior to the fix, the two print() calls will display the same info. After the fix, the values set with setInitialValues() will be reflected on the second print() call.
@JohnEstropia commented on GitHub (May 7, 2016):
@Koshub No problem. Thanks again!
You can confirm the fixed behavior with the CoreStoreDemo app.
Open the _TransactionsDemoViewController.swift_ and change the `refreshButtonTapped(_:)` method to
``` swift
@IBAction dynamic func refreshButtonTapped(sender: AnyObject?) {
let object = Static.placeController.object
print(object)
CoreStore.beginSynchronous { (transaction) -> Void in
let place = transaction.edit(object)
place?.setInitialValues()
transaction.commitAndWait()
}
print(object)
}
```
Prior to the fix, the two `print()` calls will display the same info. After the fix, the values set with `setInitialValues()` will be reflected on the second `print()` call.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Originally created by @Koshub on GitHub (May 4, 2016).
Although it is only my opinion I think it is common problem I described in (old issue) when immediately loading data after update is not loaded updated values. That issue is not so correct because I had not seen the CoreStore internal design.
As a User I would like to expect that after the transaction has finished with
commitAndWaitmethod the UI context has already been updated and all its entities are updated from root context. Currently UI context is a child context by design, but it is not updated immediately aftercommitAndWaitfinished. There is handlingNSManagedObjectContextDidSaveNotificationand fetching with UI context is not waiting forNSManagedObjectContextDidSaveNotificationprocessing finish. Also theNSManagedObjectContextObjectsDidChangeNotificationis not handled but I think UI context as child of root context should be updated with this notification too.I understand that current design is selected for performance reasons and currently UI is not waiting UI context mergings from root. Unfortunately I have to update every object I fetched with
refreshAndMergeto show in UI. It is very easy to make a bug if I forget putrefreshAndMergesomewhere in my code.Is it possible to make
commitAndWaitmethod more predictable to allow user to choose if he prefers to merge transaction with UI context or not?Thank you.
@omalovichko commented on GitHub (May 5, 2016):
It might be nice 👍
@JohnEstropia commented on GitHub (May 6, 2016):
Let me point out again that this is a only a problem when using synchronous transactions.
This is caused by the asynchronous
context?.performBlock {...}handling inNSManagedObjectContextDidSaveNotification. I've tried the following workarounds below but unfortunately all of them have huge drawbacks.1. Try using
performBlockAndWaitin theNSManagedObjectContextDidSaveNotificationhandlerThis causes a deadlock when the UI does a fetch while an asynchronous transaction is saving. It's very easy to reproduce in one of our apps that downloads a user's history of chat messages continuously after reinstall.
2. Try calling
refreshObject(_:mergeChanges:)on all the main context'sregisteredObjectsinsidecommitAndWait()This solves your problem, but the tradeoff looks bigger than the convenience it offers:
updatedObjectsproperty is empty at this point), we can only callrefreshObject(_:mergeChanges:)on all the objects. If you have thousands of objects in memory, all of them will be refreshed and merged. We can try to refresh objects as faults which would be significantly cheaper, but that would cause transient property values to break and recompute.NSFetchedResultsControllers (andListMonitors andObjectMonitors) get notified twice of the change, one from theNSManagedObjectContextDidSaveNotificationmerge and one from the explicit refresh.3. Track the transaction's
updatedObjectsand call those objects'refreshObject(_:mergeChanges:)insidecommitAndWait()This is better than (2), but
NSFetchedResultsControllers still get notified twice for each change.object.refreshAndMerge()though)I'm stuck here so far. Personally, I do not encourage synchronous transactions in the first place. I always recommend using
ListMonitors andObjectMonitors and pass those around instead ofNSManagedObjectinstances themselves, then handle all changes through the observer delegates.I do understand that this use case is sometimes unavoidable, so CoreStore exposes several
refresh***()methods. So far I'm thinking of changingcommitAndWait()tocommitAndWait(refreshObjectsImmediately:)and with default value ofrefreshObjectsImmediately = false. If you have any other ideas, let me know.@Koshub commented on GitHub (May 6, 2016):
Thank you for response. I'm currently thinking about better approach too.
@Koshub commented on GitHub (May 7, 2016):
Well I think that all existed stuff should be the same to make backward compatibility with avoiding
NSFetchedResultsControllersand other listeners get notified twice. One needs to extend functionality probably based on the next ideas.Every child context including UI context (because it is a child too) should have an ability to be configured to handle updates from his parent with
NSManagedObjectContextDidSaveNotificationnotification with onlyperformBlockcall ( withoutperformBlockAndWait). It gives an ability to make child be up to date without locks. Currently only UI context implements this thing but always and is not configured to turn off this functionality. Ability to configure child updates will gives more flexibility.The next thing is to give parent context links with all its available child contexts. Then by improving
saveSynchronously()(for example will besaveSynchronously(mergeChilds mergeChilds: Bool = false)) method ofNSManagedObjectContextextension we could get all child contexts and after(or before)try self.save()start waiting for finishing allmergeChangesFromContextDidSaveNotificationof child contexts which were configured to handle updates from their parent. After finish all blocks of child updates we continuesaveSynchronously()to do other existence work (save parent if presented, etc).Probably it could be hard to make some synchronisation of notifications queue ( of what
savewhat notification sends) or/and hard to implement correctly, but I think it could make sense.@JohnEstropia commented on GitHub (May 7, 2016):
Thanks for the ideas!
This is unnecessary for transaction contexts because CoreStore guarantees that no transactions overlap (with the exception of unsafe transactions, which is why they're "unsafe"). This means that all transactions start with fresh and correct data state. And since the main context is read-only, you are guaranteed that no other transaction is meddling with the data. Check the README explanation of how the root context propagate data to the UI context and the transaction contexts: the UI context gets data from "merges" and the transaction contexts get data from the context-nesting mechanism.
Your idea was on the right track. Maybe we can find a way to send an
isSynchronousflag toNSManagedObjectContextDidSaveNotificationso we can decide wether to useperformBlock()orperformBlockAndWait(). Something likeI'll try it out later when I have access to my PC
@JohnEstropia commented on GitHub (May 7, 2016):
It works! Try out the
1.6.5tag and tell me how it goes for you :)@JohnEstropia commented on GitHub (May 7, 2016):
Oh, no change needed on the caller side. Just update CoreStore and your current code should work as is
@Koshub commented on GitHub (May 7, 2016):
Great. Thank you for fix. Unfortunately I can't check it on real code, because I moved to plain CoreData management before I posted this issue. I will definitely check a bit later. According to changes in code I prefer blocking in sync function
saveSynchronouslyto show that the function is real sync, butperformBlockAndWaitshould work I think. Thank you for responses. Will let you know in any case.@JohnEstropia commented on GitHub (May 7, 2016):
@Koshub No problem. Thanks again!
You can confirm the fixed behavior with the CoreStoreDemo app.
Open the TransactionsDemoViewController.swift and change the
refreshButtonTapped(_:)method toPrior to the fix, the two
print()calls will display the same info. After the fix, the values set withsetInitialValues()will be reflected on the secondprint()call.@Koshub commented on GitHub (May 7, 2016):
Perfect 👍