mirror of
https://github.com/JohnEstropia/CoreStore.git
synced 2026-01-11 20:00:30 +01:00
Unexpected UI context behaviour when calling commitAndWait() #53
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
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 👍