Core Data insert objects in NSOperation and sync - objective-c

I'm saving objects from network response in NSOperation. As I understand for merge MOC's changes from background threads to main I can use mergeChangesFromContextDidSaveNotification or performBlock with parent context.
What should I prefer to use mergeChangesFromContextDidSaveNotification or performBlock?
What are the pros and cons of each merge method?
NSOperation executed in background context. So when I call performBlock will be created new thread or not?

The answer depends very much on whether your background MOC has the main MOC as a parent.
You only need to use performBlock if you are using MOCs with private/main queue concurrency.
And if you are, to get commands executed in the corresponding correct queue, you'd use performBlock.
So even if you have to use mergeChanges-- and you don't have to with child contexts-- you would nest mergeChanges in a performBlock! But the reason you don't have to with a child context is because your "background thread" MOC should could be a child of the main thread MOC. So all you have to do is save the child to merge changes into the parent. (Note that the parent also needs to be saved)
So to get back to the question:
mergeChanges if you're not pushing changes from child to parent MOC with a save in the child
use performBlock everywhere but on operations on the mainQueueMOC on the main thread if you have using multiple MOCs with different concurrency types

I'm not sure if two methods you mentioned provide equivalent functionality: you use performBlock to perform random code on the tread of the receiver managed object context. And you use mergeChangesFromContextDidSaveNotification to process NSNotification from another context save. If I got it right up to here, your only chose is the latter.
Potentially you can create a child context within NSOperation, saved data from child context propagates into parent context automatically.
To elaborate further, you would use performBlock to modify parent's managed objects from the background - it basically dispatches you to the thread of the managed object context to allow perform operations in a safe manner

Searching answer on my question I found Concurrent Core Data Stacks Performance article. This article answer that if you user old core data stack with sync data using mergeChangesFromContextDidSaveNotification method you get better performance.
Also I found apple example ThreadedCoreData. In this example they still using sync with mergeChangesFromContextDidSaveNotification.
So answer on my question is if you carry on performance you should use mergeChangesFromContextDidSaveNotification if you what simpler realization you should use nested contexts.

Related

Does accessing a property on an NSManagedObject from a background thread make the context cross that thread?

I need to use a property on my NSManagedObjectContext as a parameter in a method that runs in the background. After this method is called I then have problems from that point forward with that context even though all I did was get a property value.
From the docs:
Any time you manipulate or access managed objects, you use the associated managed object context. Core Data does not present a situation where reads are “safe” but
changes are “dangerous”—every operation is “dangerous” because every
operation has cache coherency effects and can trigger faulting.
I've been working with Core Data in multi-threaded apps for some time (also in pre-iOS 5 era, without parent contexts etc.). I suspect it does some threading voodoo (locks and stuff) under the hood. If you want to be 100% safe, in a background thread you can't call any method on a NSManagedObject if its NSManagedObjectContext has not been created on the same thread. Otherwise, Undefined Behavior may happen (I got some deadlocks, for example).

Is it possible to reuse one shared managed object context beetween threads, when each thread can work with data not related to data in other thread?

Simple example for the title question: one thread has prepared Place entity and is doing some processing on it (filling particular fields and their calculation takes time) and in the mean time the other thread has already prepared Category entity; the second "Category" thread wants to persist Category entity with save:&error causing Place entity from the first thread to be saved also while it didn't finished its processing job.
Am I right? Will I see the problem described if I use one shared moc?
Thanks!
Do not share NSManagedObjectContexts across threads. Do not share NSManagedObjects across threads. There really are no exceptions. Read Concurrency with Core Data and apply the patterns it describes.
Actually, it is possible and the method that you are looking for is:
-(void)mergeChangesFromContextDidSaveNotification:(NSNotification *)notification;
You have to create a different NSManagedObjectContext for each thread and all you have to do is to put your NSManagedObjectContext in the NSManagedObjectContextDidSaveNotification key in an NSNotification and register it to the default center. On its invokation perform mergeChangesFromContextDidSaveNotification on main thread and your changes will be merged each time you wil invoke the save:&error method.
Note: It has a little demerit, in my opinion, while fetching an object, it may belong to a different context (it will be nil after fetching) and if it does you have to fetch it differently:
NSManagedObjectID *objectID = [YourObject objectID];
YourObject *copy =(YourObject*) [managedObjectContext objectWithID:objectID];
Hope it helps. Here is a link to better understand what I am saying.

Is it safe to use managed objects loaded in background by performBlock: in main thread?

I'm creating my managed object context with NSPrivateQueueConcurrencyType concurrency type.
Also I'm using performBlock: selector to execute operations in background. So If I'm fetching some objects in background (in performBlock:), is it safe to use resulting managed objects in main thread?
As a general rule, no it is not safe to share NSManagedObject instances across threads no matter what concurrency type you are using.
However there is a library you can use to make your context(s) and object instances thread-safe. With that you can pretty much ignore all the nonsense about ensuring thread isolation between contexts and focus your efforts on the things that matter, like building out the actual functionality of your app.
I'm not 100% sure, but in my own experience I do it this way: If you are changing the variables properties, do it inside performBlock. I had one case where reading was causing some weird behavior, but in general it seems to be OK. If you want to be extra safe, use performBlock every time you touch a managed object in any way.
You will need to use a different context for each thread as explained here iOS Developer - Core data multithreading
One way to implement is described at Core Data - one context per thread implementation
Sorry, I should've search better, here is exactly my question & answer to it:
Core Data's NSPrivateQueueConcurrencyType and sharing objects between threads

NSPrivateQueueConcurrencyType context can only operate within performBlock?

I wonder if NSPrivateQueueConcurrencyType context only operate within -performBlock and -performBlockAndWait.
and NSManagedObject return from NSPrivateQueueConcurrencyType context can not accessed outside the performBlock?
I found the following sentence from apple doc "What's new in iOS 5.0"
When sending messages to a context created with a queue association, you must use the performBlock: or performBlockAndWait: method if your code is not already executing on that queue (for the main queue type) or within the scope of a performBlock... invocation (for the private queue type). Within the blocks passed to those methods, you can use the methods of NSManagedObjectContext freely.
if so, why MagicalRecord and XMPPFramework do not operate the context and NSManagedObject within the block?
I have no idea what the code in MagicalRecord and XMPPFramework does or does not do. You will need to contact them through their normal support means.
I can, however, tell you that if you create a MOC with NSPrivateQueueConcurrencyType, then the only way you should ever access that MOC or managed objects owned by that MOC is through the performBlock* API.
If you create a MOC with NSMainQueueConcurrencyType, then you can access it if you are running in the main thread, or via the performBlock* API.
If you create a MOC with NSConfinementConcurrencyType, then you must only access the MOC from the thread in which the MOC was created. If concurrency is not specified, Confinement is used as the default.
Those are the only current ways to create a MOC, and a summary of the rules for their use.
I believe MagicalRecord uses confinement and keeps a MOC per thread, but I have never used it, and have only looked at older versions of the code base, so it could have changed greatly with the advent of nested contexts.

Undoing Core Data insertions that are performed off the main thread

I'm working on some code that uses an NSOperation to import data. I'd like for the user to be able to undo the NSManagedObject instances that are created during the import operation.
From what I can tell, it's impossible to use the NSManagedObjectContext -undoManager for any operations that are performed off of the main thread. From the Core Data Programming Guide section on Use Thread Confinement to Support Concurrency, we have these two conditions:
Only objectID should be passed
between managed object contexts (on
separate threads)
Managed objects
must be saved in a context before
the objectID can be used.
This makes sense since the managed objects need to be moved from private storage (NSManagedObjectContext) to public storage (NSPersistentStore) before they can be shared.
Unfortunately, the -save: message also causes any managed objects in the undo stack to be removed. From the Memory Management Using Core Data section of the same guide:
Managed objects that have pending
changes (insertions, deletions, or
updates) are retained by their context
until their context is sent a save:,
reset , rollback, or dealloc message,
or the appropriate number of undos to
undo the change.
I've tried several things to work around this limitation, and everything eventually leads back to bulk of the work happening on the main thread (and spinning beach balls.) Any clues to getting undo working with objects created off the main thread would be very much appreciated.
--
An enhancement Radar has been submitted: rdar://problem/8977725
This answer will probably be a bit of a back and forth. If I understand the issue correctly, you are doing an import but when the import is done you want the user to be able to select what gets saved from the import?
If that is not correct, please fix my assumptions and I will update this answer.
If it is correct then what you can do is:
Change your background object creation to
NSEntityDescription *myEntity = ... //Entity from your context
[[NSManagedObject alloc] initWithEntity:myEntity
insertIntoManagedObjectContext:nil];
Store these entities in an array.
Pass the entities back to your main thread as needed.
Release on any objects you don't want to keep
Call [myMainContext insertObject:managedObject] on any you want to keep.
Perform a save on the NSManagedObjectContext.
Since these entities are not part of a NSManagedObjectContext yet they only exist in memory and should be thread safe since they are not yet tied down to a NSManagedObjectContext.
This is of course theoretical and will require testing. However it should accomplish your goal.
Not an expert, but I think what you're going to need to do is create a second context to perform the operations, then merge the two contexts together. You should be able to manage the merge as an undo step. Note, this only works if you're treating the entire set of operations as one undo step, as far as the user is concerned.
Suppose that you use a separate context for the background thread, and once it's done, push a [[backgroundContext undoManager] undo] onto the foreground thread's undo stack? I've never tried anything like that, but off the top of my head I can't think of a reason it shouldn't work.
One option may be to make your import thread persistent. Even when the thread is finished importing, it goes into an idle loop state. This way your threaded ManagedObjectContext is persisted in the proper thread. Then when the user wishes to undo a change, send a message to the thread to use the undomanager.
It's incredibly likely you've considered this and you're likely only looking for a solution using the existing undoManager, but just in case:
Since you're inserting objects and not updating existing ones, you do have the power to tag them with a transaction id as each batch is imported, deleting them in a background thread in the case of an undo. A simple incremented NSNumber is sufficient for the tag.
Inelegant, but workable.