Undoing Core Data insertions that are performed off the main thread - objective-c

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.

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).

What's the point of using performBlockAndWait in new iOS5 NSManagedObjectContext?

I am modifying my program to use the new iOS5 style.
So I simply use this code:
NSManagedObjectContext *threadContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
threadContext.parentContext = [self managedObjectContextMainThread];
//threadContext.persistentStoreCoordinator= [self persistentStoreCoordinator]; //moc.persistentStoreCoordinator;// [moc persistentStoreCoordinator];
My new background ManagedObjectContext doesn't have a persistentStore but have parent store instead.\
After that I suppose I am supposed to add
performBlockAndWait on all operation where I use all operation that use the new MOC.
I don't use that and doing just fine at least so far
performBlockAndWait is done by executing the block at the same thread and wait till it's complete.
What's the difference between that and just type the code like usual?
I mean there has to be some used, but I am totally missing here.
I can understand performBlock. That'll be like executing something in back ground. Even then it's superseded with Global Central Dyspatch.
Yes there is this new thing called Queue. Okay, if we do something on the same thread, of course everything is done consecutively. Duh.... So why the queue?
Anyone care to explain?
It is possible that the thread that execute the block is not the same with the thread that call performBlockAndWait.
For example, some core data object may only be able to be executed at main thread.
Hence, the performBlockAndWait would do it on a main thread (different thread) and block the current thread.
Also it's saver. Core data would lock things up appropriately preventing collision. If you have several thread accessing the same managed object context, you need to pull this up.
The reason for performBlockAndWait: is it will get and hold the concurrency lock to access Core Data. You can consider it a modernization of the lock/unlock approach, but that's undocumented implementation detail.
If you just execute the code directly, it won't do proper concurrency locking. This is interesting for a number of reasons:
Requests to Core Data won't be properly serialized. That is, if you performBlock: (no wait) the code could end up executing at the same time as other Core Data code, which would probably cause a problem in the coordinator or persistent store.
It… well, I actually don't think it should work. It seems to most of the time in practice, but you're running Core Data without necessary locks. Pretty sure you're into undocumented behaviour here at a minimum.
So:
performBlockAndWait: sets up an environment where your block can access Core Data via the context and waits for the block to complete.
The documentation says nothing about the thread. It's not actually documented as running on the current thread.
Even if it doesn't now, it could be changed in the future to go to secondary threads in at least some circumstances.
Read the parent point again: That's what you're supposed to rely on. The rest is just details.
performBlock: sets up an environment where your block and access Core Data via the context and does not wait for the block to complete.
The documentation says nothing about the thread. It's not actually documented as running on a different thread.
Although unlikely, a future version of the OS could decide to run the block on the current thread at a later time.
Again, the parent point is what you're to rely on. The rest is undocumented details.
I hope that helps. Basically, you're supposed to play dumber than you are when touching these calls. Let the OS do the right thing, just try not to make assumptions about what it's doing. :)
The NSPrivateQueueConcurrencyType constant sets up too many expectations for how this works.

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

What does NSManagedContext reset do?

Here's my scenario: I have a thread running heavy calculations, saving the results via core data at the end. I have the thread set up with it's own autorelease pool and it's own NSManagedContext created within the thread. The user can change the inputs to the calculation on the main thread, in which case the calculation thread is terminated (via regular checks of a NSLocked variable) and relaunched with the new inputs. Everything is working fine here.
For performance reasons, the calculation thread doesn't have an undo manager and there is only one context save at the very end. If a termination command is detected I don't want to save the results. Right now I'm just skipping the context save and releasing it, which seems to work fine.
I noticed, however, that there's a reset method for NSManagedContext. Apple's documentation on this method isn't very helpful to me. It simply states that it returns the receiver's contents to it's base state and that all the receiver's managed objects are "forgotten".
What does that mean? Is it the equivalent to reverting to the last saved version? Is an undo manager required for proper operation of this method? Any reason I should use this method instead of what I'm doing now?
It sounds like you are using the context to cache changes independent of the context on the main thread, and if you don't want those changes to be recorded, you just throw them out by deleting the "local" context. This is good enough for the scenario you are describing. -reset might be useful if you didn't want to relaunch the background thread, but just start over using the same thread (and context), but with new inputs. Since you launch a new thread (thus creating a new NSManagedObjectContext on it), -reset is probably not very useful for you in this scenario. You already pretty much doing it as Apple recommends in several of their sample codes.

Crashes in Core Data's Inferred Mapping Model Creation (Lightweight Migration). Threading Issue?

I'm getting random crashes when creating an inferred mapping model (with Core Data's lightweight migration) within my application. By the way, I have to do it programmatically in my application while it is running.
This is how I create this model (after I have made proper currentModel and newModel objects, of course):
NSMappingModel *mappingModel = [NSMappingModel inferredMappingModelForSourceModel:currentModel destinationModel:newModel error:&error];
The problem is this: This method is crashing randomly. When it works, it works just fine without issues. But when it crashes, it crashes my application (instead of returning nil to signify that the method failed, as it should). By randomly, I mean that sometimes it happens and sometimes not. It is unpredictable.
Now, here is the deal: I'm running this method in another thread. More precisely, it is located inside a block that is passed via GCD to run on the global main queue. I need to do this for my UI to appear crisp to the user, i.e. so that I can display a progress indicator while the work is underway.
The strange thing seems to be that if I remove the GCD stuff and just let it run on the main thread, it seems to be working fine and never crashing. Thus, could it be because I'm running this on a different thread that this is crashing?
I somehow find that weird because I don't believe I'm breaking any Core Data rules regarding multi-threading. In particular, I'm not passing any managed objects around, and whenever I need access to the MOC, I create a new MOC, i.e. I'm not relying on any MOC (or for that matter: anything) that has been created earlier on the main thread. Besides the little MOC stuff that occurs, occurs after the mapping model creation method, i.e. after the point at which the app crashes, so it can't possibly be a cause of the crashes under consideration here.
All I'm doing is taking two MOMs and asking for a mapping model between them. That can't be wrong even under threading, now can it?
Any ideas on what could be going on?
First, what is the crash?
Second, Core Data generally is a single threaded API. There are things you can do in multiple threads but creating a NSMappingModel is most likely not one of them. Why must you create the mapping model dynamically? If the MOMs are a known quantity then the mapping can be a known quantity as well.
update
First, the threading issue. Core Data is meant to be single threaded. However, the NSManagedObjectContext knows how to lock the NSPersistentStoreCoordinator correctly therefore you can have one NSManagedObjectContext per thread because they know how to lock correctly. However this is not the case when you are working with and creating a mapping model.
However, the error you provided is not a Core Data error per se. That error indicates that somewhere in your code you are trying to stick a nil into a set. Without seeing the code that is generating the mapping model though it is difficult to guess as to exactly where.
Have you put a breakpoint at objc_throw_exception and see what line in your code is causing this crash? If it is in something non-obvious then I would suggest there is some point in your building of the mapping model that is giving Core Data an unexpected nil.
One thing you can try is locking the NSPersistentStore and/or the NSManagedObjectContext yourself to see if that resolves the crash. However I suspect when you do that you are going to again deal with performance issues.
I ended up giving up on this problem altogether and just created the damned mapping models myself.