Do I need to worry about different contexts in NSNotification Observers - ios

I have a class that is observing when a new object 'Product' gets created so it can update details in a 'User' object.
When I save my Product object, I do it in a method on a background thread which issues the Notification that a new object has been created. I set the object in the notification's userInfo.
The observer fires when the notification is posted. I need to save some data so I create a new save block which in turn creates a new NSManagedObjectContext. I ASSUME I have to get the object in this new context.
Unfortunately, when I try to fetch the record from the new context, it returns an object with empty details. However, the object that came from the notification's userInfo is fully populated.
Am I correct in assuming I need to re-fetch the record? Is the re-fetched record empty possibly because the save isn't complete yet?

The notification our talking about is the CoreData generated when saveContext is done ? (NSManagedObjectContextDidSaveNotification).
In this case, when you receive the notification you must manually merge the context with
dispatch_sync(dispatch_get_main_queue(), ^{
[_managedObjectContext mergeChangesFromContextDidSaveNotification:notification];
});
So the context that has saved the changes in the Product entity merges his changes to other contexts.
Hope it helps.

Related

How to refetch object existing in NSManagedContext

I created UIViewController using an NSManagedObject instance, let call it A. The A has beed fetched from mainContext.
In other part of the project some kind of process have updated NSPersistedContainer using backgroundContext. During this update A changed state.
What is the best way to update UIViewController from the first paragraph. How to refetch A to update existing NSManagedObject in mainContext?
The right way to handle merging changes made on one context into another context is:
Add an observer for the NSManagedObjectContextDidSave notification.
When this notification is posted, use mergeChanges(fromContextDidSave notification: Notification) to update your context from changes made in the save operation. You can just pass the Notification along and the merge will happen, and your object will be refreshed.
An alternative is to use refresh(_ object: NSManagedObject, mergeChanges flag: Bool) for your object. Pass true for the second argument to merge in changes from the persistent store. This is probably not as good because it only affects a single object instead of everything in the context, but it's useful in some cases.
fetch in using NSFetchRequest with the predicate being the objectID! :)
the id is valid accross contextes for existing (saved) objects

What am I supposed to do in processContentChanges:?

This is my app's processContentChanges: method, which is triggered by NSPersistentStoreDidImportUbiquitousContentChangesNotification:
- (void)processContentChanges:(NSNotification *)notification {
[self.managedObjectContext performBlock:^{
// Merge incoming data updates in the managed object context
[self.managedObjectContext mergeChangesFromContextDidSaveNotification:notification];
// Post notification to trigger UI updates
#warning What do I actually do here?
}];
}
I'm using NSFetchedResultsControllers throughout my app so that the UI updates automatically when changes are received from another device through iCloud. This all seems to work, but the comment saying // Post notification to trigger UI updates was there in the template method already. Am I actually supposed to do something here, or can I safely leave things the way they are?
Well, although I haven't had confirmation of this I don't think there is anything else that needs to be done in this method as long as the following criteria are met:
You implement NSPersistentStoreDidImportUbiquitousContentChangesNotification correctly as per the template so that new content is merged to the managed object context
Your content is generated using NSFetchedResultsController objects
Your viewControllers conforms to the the NSFetchedResultsControllerDelegate protocol, and implements controllerWillChangeContent:, controllerDidChangeContent: and controller:didChangeObject:atIndexPath:forChangeType:newIndexPath
In those methods, update your views accordingly to display new content, remove deleted content, and update changed content.
If you have any objects which utilise CoreData without an NSFetchedResultsController then you might need to update these by manually re-fetching the data when NSPersistentStoreDidImportUbiquitousContentChangesNotification is posted by CoreData.

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.

Strange parent / child NSManagedObjectContext phenomenon

I have created two context like this:
// create writer MOC
_privateWriterContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[_privateWriterContext setPersistentStoreCoordinator:_persistentStoreCoordinator];
// create main thread MOC
_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
_managedObjectContext.parentContext = _privateWriterContext;
I have a NSFetchResultedController initiated with _managedObjectContext.
I know it is strange, but I am adding a record to the parent to _privateWriterContext, I am saving it.
What is surprising is that child context and so FRC gets notified about this event. Why? I have not reset-ed child, or anything else. I thought they are independent entities as long as child context will not get saved.
In #pteofil article I found this line:
When a change is made in a context, but not saved, it is visible to all of its’ descendants but not to its’ ancestors.
.. it is pushed to the persistent store (via the persistent store coordinator) and becomes visible to all contexts connected to the store.
This is not supposed to happen. Adding an NSManagedObject ('record' ) to the parentContext, will not make the child aware of this object automatically. Only when you make the childContext execute a fetch, then it will fetch from the parentContext.
To figure out what is going on in your case, here are some suggestions:
figure out when a fetch is executed on the childContext (this is done by the fetchedRestultsController when you set it up. Check if that fetch happens before or after you add the managedObject to the parentContext).
set breakpoints in all four delegate callbacks of the fetchedResultsController to find out for which object it is calling the methods (and see if it is the object you just added to the parentContext).
make absolutely sure you know which context you are sending messages too.
I have a used a similar approach, but different: the childContext is the context is used to parse in new data (on a private queue), and when this parsing is done, the chid calls save:. This will save the changes up to the parent, which is in my case the mainQueueContext. This call to save: will cause the mainQueueContext to receive all the newly parsed objects, and any fetchedResultsController using that mainQueueContext will then call it's delegate methods for the new/changed/updated/delete objects.
You could try inverting your child/parent relationship too and see if it works as described in the docs, just to find out whats going on.
I strongly recommend avoiding parent-child context setups. Our book goes into detail why they often lead to strange behaviour: https://www.objc.io/books/core-data/
Short story: They're not as independent as you might think.
Use multiple context that share a single persistent store coordinator if you need more than a single context.

NSManagedObjectContext - How to associate childcontext data with parentcontext data?

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.

Resources