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

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.

Related

What is the best practice to pass object references between two queues with CoreData?

I am facing a decision to be made for an applications architecture design.
The Application uses CoreData to persist user information, the same information is also stored on a remote server accessible by a REST-Interface. When the Application starts I provide the cached information from CoreData to be displayed, while I fetch updates from the server. The fetched information is persisted automatically as well.
All of these tasks are performed in background queues as to not block the main thread. I am keeping a strong reference to my persistenContainer and my NSManagedObject called User.
#property (nonatomic, retain, readwrite) User *fetchedLoggedInUser;
As I said the User is populated performing a fetch request via
[_coreDataManager.persistentContainer performBackgroundTask:^(NSManagedObjectContext * _Nonnull context) {
(...)
NSArray <User*>*fetchedUsers = [context executeFetchRequest:fetchLocalUserRequest error:&fetchError];
(...)
self.fetchedLoggedInUser = fetchedUsers.firstObject;
//load updates from server
Api.update(){
//update the self.fetchedLoggedInUser properties with data from the server
(...)
//persist the updated data
if (context.hasChanges) {
context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy;
NSError *saveError = nil;
BOOL saveSucceeded = [context save:&saveError];
if (saveSucceeded) {
//notify the App about the updates
//**here is the problem**
}
};
}];
So the obvious thing about this is, that after performing the backgroundTask, my self.fetchedLoggedInUser is not in memory anymore, because of its weak reference to the NSManagedObjectContext provided by the performBackgroundTask() of my PersistentContainer.
Therefore, if I try to access the information from another Model, the values are nil.
What would be the best practice to keep the fetched ManagedObject in Memory and not have to fetch it again, every time I want to access its values?
A) In the Documentation, Apple suggests using the objectID of an ManagedObject, to pass objects between queues
Passing References Between Queues
NSManagedObject instances are not intended to be passed between
queues. Doing so can result in corruption of the data and termination
of the application. When it is necessary to hand off a managed object
reference from one queue to another, it must be done through
NSManagedObjectID instances.
You retrieve the managed object ID of a managed object by calling the
objectID method on the NSManagedObject instance.
The perfectly working code for that situation would be to replace the if(saveSucceeded) Check with this Code:
if (saveSucceeded) {
dispatch_async(dispatch_get_main_queue(), ^{
NSError *error = nil;
self.fetchedLoggedInUser = [self.coreDataManager.persistentContainer.viewContext existingObjectWithID:_fetchedLoggedInUser.objectID error:&error];
(...)
//notify the App about the updates
});
}
But I think this may not be the best solution here, as this needs to access the mainContext (in this case the persistentContainer's viewContext) on the mainQueue. This is likely contradictory to what I am trying to do here (performing on the background, to achieve best performance).
My other options (well, these, that I came up with) would be
B) to either store the user information in a Singleton and update it every time the information is fetched from and saved to CoreData. In this scenario I wouldn't need to worry about keeping the NSManagedObject context alive. I could perform any updates on a private background context provided by my persistentContainer's performBackgroundTask and whenever I'd need to persist new / edited user information I could refetch the NSManagedObject from the database, set the properties, save my context and then update the Singleton. I don't know if this is elegant though.
C) edit the getter Method of my self.fetchedLoggedInUser to contain a fetch request and fetch the needed information (this is probably the worst, because of the overhead when accessing the database) and I am not even sure if this would work at all.
I hope that one of these solutions is actually best practice, but I'd like to hear your suggestions why/how or why/how not to handle the passing of the information.
TL:DR; Whats the best practice to keep user information available throughout the whole app, when loading and storing new information is mostly done from backgroundQueues?
PS: I don't want to fetch the information every time I need to access it in one of my ViewControllers, I want to store the data on a central knot, so that it is accessible from every ViewController with ease. Currently the self.fetchedLoggedInUser is a property of a singleton used throughout the application. I find that this saves a lot of redundant code, using the Singleton makes loading and storing the information clearer and reduces the access count to the database. If this is considered bad practice I'd be happy to discuss about that with you.
Use a NSFetchedResultsController - they are very efficient and you can use them even for one object. A FetchedResultsController does a fetch once and then monitors core data for changes. When it changes you have a callback that it has changed. It also works perfectly for ANY core-data setup. So long as the changes are propagated to the main context (either with newBackgroundContext or performBackgroundTask or child contexts or whatever) the fetchedResultsController will update. So you are free to change your core-data stack without changes your monitoring code.
In general I don't like keeping pointers to ManagedObjects. If the entry is deleted from database then the managedObject will crash when you try to access it. A fetchedResultsController is always safe to read fetchedObjects as it tracks deletions for you.
Obviously attach the NSFetchedResultsController to the viewContext and only read it from the main thread.
I came up with a very elegant solution in my opinion.
From the beginning I was using a Singleton called sharedCoreDataManager, I added a property backgroundContext that is initialized like so
self.backgroundContext = _persistentContainer.newBackgroundContext;
_backgroundContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy;
_backgroundContext.retainsRegisteredObjects = YES;
and is retained by sharedCoreDataManager. I am using this context to perform any tasks. Through calling _backgroundContext.retainsRegisteredObjects my NSManagedObject is retained by the backgroundContext, which is itself (like I said) retained by my Singleton sharedCoreDataManager.
I think this is an elegant solution as I can access the ManagedObject threadsafe from the background anytime. I also won't need any extra class that holds the user information on top. And on top of that I can easily edit the user information at anytime and then call save() on my backgroundContext if needed.
Maybe I am going to add it as a child to my viewContext in the future, I'll evaluate the performance and eventually update this answer.
You are still welcome to propose a better solution or discuss this topic.

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.

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

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.

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.

iOS 6 What is the standard way to deal with background saving of managed objects (in 2013)?

Last year I used RestKit 0.10 to seamlessly download and save core data objects in background. However, when I tried to use restkit in 2013, I noticed that they have taken out the ActiveRecord pattern, which I relied upon to abstract away all the unpleasantness of background saving.
I found that the ActiveRecord pattern exists in MagicalRecord framework, but most of the documentation I could find is for version 2.x, while my cocoapods install 3.x.
I spent the last 2 hours searching, and find a lot of answers that are really out of date and no longer work for these new frameworks.
This poses the question: what the standard/easiest way to deal with saving core data objects in background using frameworks available in 2013? Should I try some other framework?
If you don't use any external library like Magical Record or RestKit, but simply go for the all manual stuff, you can take advantage of the new NSManagedObjectContext APIs.
You can now have contexts nested with a parent-child relationship and you can also tell each context to perform a block in it's own thread. My advice, therefore is to have the following structure for your application:
1) A background saving context. This will be the only context that saves and reads data directly to/from the database.
2) A context initalized on the main thread that will be your point of access for everything you need to do in the application, especially updating the UI. This context will be a child of the saving context.
3) As needed, you'll create background contextes that perform work on background threads, e.g. loading data from the network and serialize this data in NSManagedObject instances. This contextes will be children of the main context.
4) Every time you call -[NSManagedObjectContext save:] on a context, you should also call the same method on it's parentContext. To do this you could have a convenience method in a category on NSManagedObjectContext that reads something like this:
- (void)saveSelfAndParent {
[self save:NULL];
[self.parentContext performBlock:^{
[self.parentContext saveSelfAndParent];
}];
}
This is already a thread safe configuration and your changes will propagate the changes up to the database. Note that as the saving context will have no parent (and thus self.parentContext will be nil), the performBlock: won't crash the app.
Here's an example what you need to do to create a new entity assuming you kick off your background work using Grand Central Dispatch (GCD):
dispatch_async(dispatch_async_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType: NSPrivateQueueConcurrencyType];
context.parentContext = mainContext;
// do some expensive job
...
// initialize a new NSManagedObject instance using the information we calculated
NSManagedObject *myObject = ...;
// once we're done, let's save the context
[context saveSelfAndParent];
});
Note that we initialized the context with a private queue concurrency type (NSPrivateQueueConcurrencyType) which tells the context that he's a background context. That is very important!
That's all! :)
For more information refer to the NSManagedObjectContext Class Reference.

Resources