NSManagedObjectContext - How to associate childcontext data with parentcontext data? - ios

I'm confused on how the a child MOC (NSPrivateQueueConcurrencyType) works with a parent MOC (NSMainQueueConcurrencyType) with respect to the following scenario. I need to has a background thread check a web server for new or updated data. When something is received, I need to save or update the parent MOC. Sounds simple enough, and I see various examples. However, I'm still confused on a couple of things.
When processing data in the background thread, I can easily save the object in the child MOC. However, in my data model, I have relationships set up as recommended. So, for example, a ConversationThread will contain an array of Messages. Each Message will have Message.parentConvoThread set in the relationship. So, whenever I'm getting new messages from the server...
How do I associate the new Message object, which is created in the child privateMOC, with the ConversationThread (currently in the parent mainMOC)?
Now, say that I'm getting updated personal info for the person who wrote the message. I see they have updated data on the server, so I need to update their data in app. In the privateMOC...
How do I get the actual object (say it's MyContact) from the mainMOC to explicitly update? or...
If I create a new MyContact in the privateMOC, how do I merge that with the currently existing MyContact in the mainMOC? ...or does it automagically happen somehow? (<- I've read a lot of older threads that say you to use NSManagedObjectDidChangeNotification and manually merge but that this isn't necessary anymore...but how/why?)
Finally, a couple of questions about searching...
Can a search against the child privateMOC return results from the parent mainMOC (say if an entity exists in the parent but not the child)?
If the answer to #1 is true, what happens if the entity exists in both but hasn't been merged?
I'm quite confused on how they work together. Any help is greatly appreciated.

NSManagedObjectContexts are in memory caches of the data from an NSPersistentStore. A fetch on a child context will be executed through the parent context on the NSPersistentStore, and the data from the objects will be retrieved from either the cache in the child context, the parent context, or all the way from the persistent store (wherever it can first find the data).
If you are fetching from a child context, the results will be retrieved through the parent context, and you can expect this fetch request to return objects as though you fetched from the parent context.
Going the other direction, as long as all the changes you have made to your child context have been saved, those changes will be reflected in the parent context, because core data automatically handles the merge from child to parent.
The only trick is if you have references to objects in the child context, and changes are saved in the background to the parent context, you will either need to re-fetch those objects on the child context to get changes from the parent, or you can manually merge the changes on the parent's NSManagedObjectContextDidSaveNotification. See this post for more information: How to keep a child NSManagedObjectContext up to date when using bindings.

Related

Use NSManagedObject in child NSManagedObjectContext instead of its parent

I have two MOCs: the first is the root context. When I save this context, the changes are saved to the persistent store coordinator. The second MOC has the first MOC as the parent. When I save the second MOC, I also have to save the first MOC in order to save the changes in the second MOC to the persistent store coordinator.
I use the second MOC to let the user edit an object. He can save or cancel the changes. When he saves the changes, all MOCs are saved. When he cancels the changes, I call rollback() of the second MOC.
Unfortunately, the object comes from the first MOC. This means, I execute an NSFetchRequest to fetch the object on the first MOC. Then I create the second MOC in which the user can edit the object. But there is a problem: when the second MOC should change something, for example delete an object that is contained in an array of the original object the user wants to edit, this is not possible, because a MOC can only delete objects that have this MOC as the context. But the object was fetched in the first MOC.
That's why I need to "transfer" somehow the object from the first MOC to the second MOC before the user edits the object. I don't want to fetch the object again with a NSFetchRequest or something, there must be a better way…
Is this possible? Or do you recommend to do this completely different, maybe without parent contexts?
This is where the objectID property of NSManagedObject will come in handy.
Ask the object for its ID
let objectID = myManagedObject.objectID
Ask the child context for a managed object with that ID
do {
let childManagedObject = try childContext.existingObjectWithID(objectID)
print("\(newObject)")
} catch {
}
I think you might be complicating your time with this. Unless it is completely necessary for proposed changes to also be saved there should be no reason to have two contexts for this. There are multiple ways for you to handle a temporary data which you can use to compare your actual record to without having it stored twice.
Why don't you just create a copy of the NSManagedObject and handle the information correction by comparison or simply replacing the original NSManagedObject with the data of the copy and then save it? I personally like this setup a bit more since all I have to do is either compare individual properties when update is desired.
When a full update is required than you can simply work directly on the one NSManagedObject without worrying about copies since you will probably be replacing the entire thing anyway. Like I said, there are other ways to handle, but if it is absolutely necessary for you to have both contexts then looking for a comparison of each property value to the replaced value and then simply save the one in the parent context.

UIManagedDocument parent context object insertion on a background priority queue not updating in UI using child context

I'm trying to implement some basic UIManagedDocument import/export functionality into my app, mainly for dev so that I can easily inspect the document contents and more crucially preserve a set of test data when I start trying to iterate on my CoreData models.
All I am trying to do is load some JSON data from a local file and inject it into my apps UIManagedDocument. The UIManagedDocument's ManagedObjectContext contents are visualised in my app using some Core Data Table View Controllers from the Stanford iOS courses.
I thought I'd try to write this with some threading to keep the UI responsive and to learn how to do it. So I've done something like this
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
// read JSON data file
// parse JSON into dictionaries etc
// iterate over dictionaries
// try to create a new core data entity with my document's ManagedObjectContext
} );
At first I thought this was working. No errors, asserts, crashes triggered and upon looking at my CoreData TableViews I could see the newly added objects in my UI. Unfortunately the newly added objects were seemingly never saved back to the store. I even hooked up to listen to the NSManagedObjectContextDidSaveNotification from my UIDocument's managedObjectContext and saw it wasn't triggering on pressing the home button, like it usually does if it has some changes performed in the app with my UI pending. Infact even doing these operations in the UI wouldn't cause the notification and saving to occur so it was clearly not happy.
I unrolled the code from within the background queue and ran it on the main thread synchronously and everything worked ok, the new data was saved correctly.
I started reading about the complexities of threading and coredata, the documentation seemed to suggest using the UIDocument's ManagedObjectContext's parent ManagedObjectContext to perform operations on in the background so I tried doing the same code again using this parent context, so as follows
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
// read JSON data file
// parse JSON into dictionaries etc
// iterate over dictionaries
// try to create a new core data entity with my document's ManagedObjectContext parent ManagedObjectContext
} );
This time for some reason the CoreData TableView controllers no longer updated to show the newly injected objects. Even after explicitly calling save on the parent context, nothing appeared. However on quitting the app and reloading the app the newly injected objects did seem to be added correctly. Interestingly at this point i'd left a fetchrequest with a cachename specified and that threw up an error on this first run of the app after injecting the objects this way. I guess somehow the way the object had come from the parent context directly perhaps invalidated the cache somehow, that's still something I don't fully understand. Even changing the cache to nil didn't fix the issue of the table views not updated the same session as when the objects were injected into the parent context.
Looking elsewhere I've seen some uses of the managedObjectContext performBlock suggested. Another case where someone has said you must call
[document updateChangeCount:UIDocumentChangeDone]
after all changes to ensure the saving is performed, or perhaps using
- (void)autosaveWithCompletionHandler:(void (^)(BOOL success))completionHandler
instead. Though elsewhere I've seen mentioned that saving should be enough to push context contents through the hierarchy. Does saving only work from child -> parent and not from parent -> child.
Or am I just doing it wrong?
Anyone's time and help is really appreciated! Cheers.
please look at the 'Parent/Child Contexts' section in the Multi-Context CoreData.
Whenever a child MOC saves the parent learns about these changes and this causes the fetched results controllers to be informed about these changes as well. This does not yet persist the data however, since the background MOCs don’t know about the PSC. To get the data to disk you need an additional saveContext: on the main queue MOC.
I do saving to the PSC n following way:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
{
// read JSON data file
// parse JSON into dictionaries etc
// iterate over dictionaries
NSError* error;
if (self.managedObjectContext.hasChanges)
{
[self.managedObjectContext save: &error];
}
// Save main context
dispatch_sync(dispatch_get_main_queue(), ^
{
[[AXContextHelper sharedInstance] saveMainContext];
});
}
After looking into all the suggestions I could find for similar problems, none of them seemed to help.
The scenario i was describing in the end had the store stuff to file handled ok, but the UI not updating. At the time when I do this import/export I'm in a view ontop of the core data table view controller that doesn't update when I inject all these JSON objects, so in the end all I did was force that controller to re-fetch it's data. I believe the NSFetchedResultsController is meant to monitor the managedObjectContext and update the fetch request as required. For whatever reason, this wasn't working.
With the force re-fetch called, everything seems to work ok. The newly injected entities appear in my tables, and are saved to the store files.
Either there are some bugs in these objects, or I'm still using it wrong but I see other problems... my import/export data both work now on background threads with the parent context. What I've noticed today is that I can use the UI to properly insert and edit some objects using the child context on the main thread. I don't deliberately call anything at the moment after making these edits so i guess the edits are are still pending till core data decides to save. If i then go to my export feature and use it using the parent context then i find the newly edited or inserted objects aren't there.
Clearly there's no messaging happening in time from child -> parent, and messaging from background thread edits on the the parent itself just doesn't seem to work.. I never get any notifications for the edits i perform in a thread on the parent context, even though they seem to work. If I force the save to happen via the documents then the passed in dictionaries show no edits being made, even though the edits are saved correctly to store files somehow.
I guess i now need to force my document to save after every operation, or at least before every operation i'm about to do on another thread to ensure the parent/child are both in sync.
Still the problem I originally described I've managed to get around, but I'm still left wondering quite how I should be using the parent and child context of a UIManagedDocument to avoid encountering these issues.

NSMangedObject attributes value missing, when using more than one context simultaneously

I am using three NSManagedObjectContexts (moc) as A, B, C (parent, child1, child2 respectively) for my project.
A(parent) is in private queue, only for saving after saving either of child moc saved
B(child1) is in main queue, is used for updating UI part
C(child2) is in private queue, is used for saving/updating data to core data from server response
Now my problem is when i am trying to load/populate a table with fetched data from core data using B, I miss attributes of entities. That means all attribute values becomes nil.
What I think happens is: I am saving data using context C and fetching data using B. Is it the reason for missing attributes?
I just ran into a similar situation where when trying to fetch from a child context would return the correct number of objects but all the properties would be nil. The culprit in my case was I had called
- (void)setPropertiesToFetch:(NSArray *)values
on my fetch request. Once I removed this line I got the properties populated. I was fetching NSManagedObjects and the documentation says:
This value is only used if resultType is set to NSDictionaryResultType.
So I would think it should've just been ignored, but in fact it breaks stuff. Oddly though if you leave that set properties call in and execute the fetch in the root context (the context that has no parent) then everything works normally. All this was in iOS 7.1

how to fetch persistent store copy of NSManagedObject regardless of current context state

I'm working on an app that uses CoreData alongside a server API.
I'm simplifying a bit, but the tricky part is that when I update a relationship (add or remove an object from the relationship), besides saving this to CD, I also need to send separate HTTP requests to the server to add or delete such objects.
For instance (I'm obviously skipping the JSON <-> CD parsing aspect of this), say I:
retrieve from the server object O with relationship R (to-many) initially holding another object r0. So that after a fetch to the server, my main context holds O.R = [r0].
I then remove r0 and add r1. Now the main context holds O.R = [r1].
Currently, what I do when I want to commit this back to the backing SQL in the app and also the server is dispatching an async block on a private GCD queue. The block:
Creates a temp context that only shares the store coordinator with the main context.
retrieves the "saved" (in the SQL db) version of object O by ObjectID using the temp context.
Does a diff by ObjectID between the elements in the version of O in the main context and the version just retrieved via the temp context. In this way I can tell that the version just retrieved via the temp context has O.R = [r0], while the current version from the main context has O.R = [r1].
From the above, I know I have to issue one call to delete r0 from the server and another to add r1.
Last, I save the main context, since as far as CoreData is concerned, O.R = [r1] is indeed the new state. (remember that figuring out what was deleted and what was added on a per-object basis is only necessary for the server update).
Now the problem is, that I want to do this using child contexts and -perfromBlock: instead. But if I create a child context of the main context instead of a temp context that only shares the store coordinator with the main context, I can't seem to find a way to access the "old" version of the object in question from the SQL. That is, after the change, the main context has O.R = [r1]. And if I fetch the same object via the child context by id, I also get that O.R = [r1]. Also, I tried calling -refreshObject: on the child context to see if this would force the child to refetch from the SQL, but it still reflects the current state of the main context. I think this has to do with a cache that I don't know how to clear.
Point being, is there a way to force the child context to fetch from the persistent store instead of the cache? Or is there another way to fetch the "old" state of the object in question?
BTW, neither -changedValues nor -changedValuesForCurrentEvents seem to do the trick either.
There is no way to force the child to get the saved state instead of the current state.
You either need to fetch the objects before modifying them or you need to have a separate sibling. Another option would be to have a completely separate stack including the PSC.
Update
Siblings are two contexts that extend from the same NSPersistentStoreCoordinator or from the same parent context.
Fetching beforehand. Create the child context, fetch the objects that are going to be modified in the child. Then modify the objects in parent. The children will not be modified because context changes do not get pushed down.
this is a hack. Core Data is not designed to work like this. CD is an object graph first and it works very hard to keep everything in sync. I would re-think what you are doing and find a cleaner solution.
Update
There is no documentation on how iCloud works internally. However, if I were to write it I would listen for the NSManagedObjectContextDidSave notification and then based on the insert, update and delete sets I would build the transaction log.

iOS5 NSFetchedResultsController not getting delete updates

I have a NSFetchedResultsController tied to my main managed object context. It is in charge of keeping data for a table view in my main view.
I have an NSOperation running on a background thread that refreshes/deletes the managed objects that the fetched results controller is keeping track of. I create a child context (private concurrency type / parent = main managed object context) in the nsoperation and insert/delete objects. When it is finished, it saves its context, as well as the parent context.
What's interesting and very frustrating is that this works fine in iOS 6. When I insert or delete objects, my fetched results controller is notified of the changes and everything works as expected. However, on iOS 5, everything works except for deletes. The fetched results controller is not notified of a delete. However... if I manually refresh the fetched results controller (making it nil and refetching the same predicate) then it will show the expected result.
Also, when I register for change/save notifications on the main context, I can see that the user info dictionary contains the objects that I've deleted... even in iOS 5!
One issue that I thought it may be, but I don't think holds because I've removed the factors, is that this object is in a many to one relationship with another object. The object I am deleting/inserting is an "employee" and it has a relationship with a "department". The employee is set to nullify and the department is set to cascade.
As I said, this works fine in iOS6 but not in iOS5.
Any tips would be very helpful.
This bug is due to saving to the persistent store. This child context saves itself, then calls perform block on it's parent, the main managed object context. When the main managed object context saves, it triggers a background context to write to the persistent store. When I removed the background context save, the fetched results controller updated as expected.
Something interesting that I found that was probably causing this was that the managed object was leaking every time I tried saving to the store. Not exactly sure how to fix this yet, but it's good to know the reason for it.

Resources