Passing objects from a parent context in main queue to a child in private queue - ios

I have an NSMutableArray property in a class where I keep reference to some managed objects of an NSManagedObjectContext which I called mainContext and that is associated to main queue. In this same class, I create a privateContext in a private queue and I set it as a child of the mainContext.
I'd like to pass the objects in my NSMutableArray (self.entities), which belong to the mainContext, to its child privateContext and, at the same time, keep a reference of such objects once they are in the privateContext in another array (self.tempEntities). I want to keep these references because I'll insert new objects later in privateContext and I'd need to easily know which of the objects that are at that moment in privateContext came from its parent mainContext.
I'm not sure if this way of doing this is correct:
for (MyEntity *entity in self.entities) { // this is main thread
[self.privateContext performBlockAndWait: ^{
[self.privateContext objectWithID:entity.objectID];
[self.tempEntities addObject:entity];
}];
}
Or this will cause any problem later. Or maybe is there another and better way to do this?
Thanks
EDIT: How will be the parent context updated in this case when the child is saved? I mean: the purpose of passing to the child context the objects in the parent, in my scenario, is that I need to compare in the child context its new objects with the ones that were passed from the parent. Then, maybe I need to delete some objects that came from the parent, and/or replace some of the objects that came from the parent with some of the news in child, and/or insert some of the new objects in child into the parent.
Would calling [self.privateContext save:nil]; replace all objects in parent context with the objects in the child, or how is the merge handled?
Thanks again

You are still accessing the main thread object in a background thread, which is not allowed. This could work (if it does not trigger an automatic fetch), but it is safer to get the object ID before entering the background thread.
NSManagedObjectID objectID = entity.objectID;
[self.privateContext performBlockAndWait: ^{
MyEntity *bgEntity = [self.privateContext objectWithID:objectID]
// do something with the entity, e.g. update it and save.
}];
Make sure you do everything you need to do in the background in the block. I would advise not to assign these objects to the controller (as you seem to be doing with self.tempEntities) where they are again available to the main thread. If they are accessed on the main thread, your app will crash.
Another improvement would perhaps be to use just one context.
NSArray *objectIDs = [self.entities valueForKeyPath:#"objectID"];
[self.privateContext performBlockAndWait ^{
for (NSManagedObjectID *objectID in objectIDs) {
MyEntity *bgEntity = [self.privateContext objectWithID:objectID];
// process...
}
}];
As for updating the main context: when you save a child context, all it does is "push up" the changes to the parent context. The parent context is now aware of the changes, but of course it will not automatically update your arrays.
There two avenues: the more complicated and risky one is to listen to the NSManagedObjectContextObjectsDidChangeNotification. In the notification handler, you could update your array and the table view. Still, this is risky because there is no memory or performance optimization etc.
Better: use a NSFetchedResultsController and implement the NSFetchedResultsControllerDelegate methods to update your table or other data structures. You get lots of great functionality for free.

Related

How to handle NSManagedObjectContext and NSManagedObject creation and editing on multiple threads?

I have an application where I’m using Core Data. It’s my first time with it so I’m using the same Core Data stack that Apple provides in the AppDelegate.m .
The problem I’m facing is described below :
I have a method called firstSaver which performs operations as :
+(void) firstSaver {
// 1) get some values from system
// 2) do some processing on those values ( This takes considerable time)
// 3) create a NSManagedObject instance of entity A ,say mObj ,by filling in the processed values. I create multiple objects. In this step, I use the main managedObjectContext that is provided by the AppDelegate to me.
// 4) pass this NSManagedObject to secondSaver like :
[self secondSaver : mObj];
// 5) save the managedObjectContext.
}
the second method works as :
+(void) secondSaver : (NSManagedObject *)someObj {
// 1) again fetch some values, this too takes considerable time.
// 2) create a NSManagedObject which is instance of entity B, fill the processed values, attach this instance to the someObj instance.
return;
}
Note that A is related to B by a one-to-many relationship, i.e. A contains a NSSet of B.
As seen, the two calls require considerable time to complete and it freezes the UI. I don’t want it to happen hence I created a serial dispatch_queue and called the firstSaver on it using dipatch_async.
The problem is that as the instance of NSManagedObjectContext has been created on the main thread, and if I access it inside dispatch_async, it results in EXEC_BAD_ACCESS.
What could possibly be the correct approach to handle this scenario and use proper managed object context for dealing with multithreading ? Any help will be appreciated.
You should create new child managed object contexts to use, with private queue type and the main context as the parent. All of your logic should be in a performBlockAndWait: and that's where you do your long query and create the new object.
To use the mObj here you need to get its objectID and then use existingObjectWithID:error: to get the appropriate version in the child context. Then you can connect your new object to the existing object but in the correct context.
When you're done, save the child context and then use performBlock on the main context and save it.
// move to background thread
// create child
// set parent
// perform block and wait on new context
// find the existing object for mObj ID
// search, create, associate
// save
// perform block on the main context
// save
Multithreadding with Coredata is a pain in the ass. You should avoid this if possible. If the creation or modification of an MO takes long time, create the data or modify the exiting one in a background thread and then do a performSelectorOnMainthread for all Coredata actions.

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.

How to replace an object in a certain context with another object in other context?

I've, let's say, a mainObject in an NSManagedObjectContext in the main queue ("main context"). On the other hand, I've a privateObject in another NSManagedObjectContext in a private queue ("private context").
I want to delete mainObject from maon context, and insert there the privateObject in the private context. I tried this:
[mainContext insertObject:privateObject];
but I get a crash:
'An NSManagedObject may only be in (or observed by) a single NSManagedObjectContext.'
I also tried this:
[mainContext objectWithID:privateObject.objectID];
and this:
NSManagedObject *managedObject = [privateContext existingObjectWithID:privateObject.objectID error:&error];
[mainContext insertObject:managedObject];
but I get the same error.
Is there any way to insert an object in a context into another context? Or should I save the private context and then fetch such object from the main context? Or is there another different and better way to do this?
Thanks
Why not copy the object's property?
For example make a new fresh managed object in main context and copy the properties from the private managed object. It would be much clear to understand and would be easily managed.

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.

Is it valid to mix the "1 moc per thread" strategy with "child moc" strategy?

Until now I always used a "main moc" for the main thread, initialised like this:
[[NSManagedObjectContext alloc] init];
and then I have NSOperation subclasses with their own moc that import data from the webservice, and I merge in the "main" moc on save observing NSManagedObjectContextDidSaveNotification
But now I need the ability to add "temporary" objects that the user can commit (or not) later. So it looks like a child context is the perfect fit, and in order to use child context I changed the initialization of my "main moc" to
[[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
The question is: can my current structure with NSOperation subclasses with their own moc (initialised without a type in their own thread) have problems if used along with the child context strategy? I don't think so, but I don't find much about mixing those strategies.
Note that I want to maintain the NSOperation subclasses and I don't want to use child contexts also for importing my data, because it suffers on performances, see http://floriankugler.com/blog/2013/4/29/concurrent-core-data-stack-performance-shootout
Moreover, when I create a new child of my main thread (that is of type NSMainQueueConcurrencyType), can I create it that child with type NSMainQueueConcurrencyType, continuing to work with my objects in the main thread as usual? Or am I forced to use NSPrivateQueueConcurrencyType, and use performBlock: for every operation on my objects?
I'm asking because is not clear from the documentation if using 2 moc on the same thread (the main thread in this case) could be a problem.
UPDATE:
Finally I implemented and used this solution on production and there are not problems so far. The only thing that I needed to do is to avoid merging on my NSManagedObjectContextDidSaveNotification notification when the moc has a parentContext (we don't want to merge mocs with a parent context, because they manage the merge themselves, but obviously the notification is triggered also for save on this kind of moc)
Yes, you can have multiple main queue context mocs, for exactly the reason you say - you create a temporary editing context for editing data which is then saved or discarded depending on user action.
As for mixing and matching with your operation queue contexts - that shouldn't be a problem. If you're merging back to the parent context, then any child contexts will pick up that data the next time they fetch.
Actually, that's indicated when working with multiple threads. Here, I've wrote an article exactly about this. The slave mocs mentioned in it, are designed exactly for working with operations, each operations on it's own slave moc.

Resources