Is NSFetchedResultController performFetch thread safe? - iphone

I'm working with NSManagedObjectContext in multithreads.
I wonder if it request lock before call NSFetchedResultController performFetch.
Shall I do this
[moc lock];
NSError *error = nil;
if (![fetchedResultsController_ performFetch:&error]) {
//TODO: add fetch error handler
}
[moc unlock];
Or just
NSError *error = nil;
if (![fetchedResultsController_ performFetch:&error]) {
//TODO: add fetch error handler
}

If your fetchedResultsController is shared across multiple threads, then not only must you lock the managed object context before performing the fetch, but it must also be locked while you use any object returned by that fetch. Naturally, that's not a very easy thing to guarantee, and tends to limit the benefits of doing things on mulitple threads in the first place.
Applications using Core Data are strongly encouraged to use one managed object context per thread. See Concurrency with Core Data for more information.

Related

Adding to UIManagedDocument from GCD background queue?

I am creating and adding a number of managed objects to Core Data from a background queue. My understanding was that I could not access the context from the background thread so I was using performBlock to schedule adding to Core Data back onto the same queue that the context was created on. This works just fine ...
My question is during testing I noticed that by removing [moc performBlock:^{ ... }]; the application still performs as expected (maybe even fractions of a second faster) Do I need the performBlock? I would assume I do and its just working (for now :) in a none-threadsafe fashion, I just wanted to check to make sure my understanding is not flawed.
dispatch_queue_t backgroundQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_async(backgroundQueue, ^{
// GET DATA
// PROCESS DATA
NSManagedObjectContext *context = [[self managedDocument] managedObjectContext];
[moc performBlock:^{
// ADD TO CORE DATA
[Core createRodInContext:context withDictionary:fuelRodDictionary];
}];
});
EDIT: Added implementation for createRodInContext ...
+ (Rod *)createRodInContext:(NSManagedObjectContext *)context withDictionary:(NSDictionary *)dictionary {
// CREATE
Rod *rod = [NSEntityDescription insertNewObjectForEntityForName:#"Rod" inManagedObjectContext:context];
// POPULATE
[neo setDataCode:[dictionary objectForKey:#"dataCode"]];
[neo setDataName:[dictionary objectForKey:#"dataName"]];
[neo setDataReference:[dictionary objectForKey:#"dataReference"]];
...
return rod;
}
In the background thread you have to use [moc performBlock:^{ ... }] to insert (and populate) a managed object in the main managed object context.
Omitting the performBlock means that you use the managed object context (which was created on the main thread) also in a different thread (which is associated with the background queue).
This might work by chance, but as soon as the main thread accesses the MOC in the same moment as your background thread, the results are unpredictable because (as you already said), a MOC is not thread-safe.
See also Concurrency Support for Managed Object Contexts
in the Core Data Release Notes for OS X v10.7 and iOS 5.0:
Confinement (NSConfinementConcurrencyType).
This is the default. You
promise that context will not be used by any thread other than the one
on which you created it.
But also for the other concurrency types (private queue, main queue), you always have to use performBlock (or performBlockAndWait) unless your code is already executing on the queue associated with the MOC.

Core Data's NSPrivateQueueConcurrencyType and sharing objects between threads

iOS 5 introduced a new way to quickly fetch data on a background thread by initializing the MOC using NSPrivateQueueConcurrencyType and then doing the fetch in performBlock:
One of the rules of thumb of Core Data has been that you can not share a managed object between threads/queues. Is it still the case with performBlock:? Is the following:
[context performBlock:^{
// fetch request code
NSArray *results = [context executeFetchRequest:request error:nil];
dispatch_async(dispatch_get_main_queue(), ^(void) {
Class *firstObject = [results objectAtIndex:0];
// do something with firstObject
});
}];
still unacceptable since I'm sharing my results array/objects between the bg queue and the main queue? Do I still need to use the managed object IDs to do that?
When you use NSPrivateQueueConcurrencyType you need to do anything that touches that context or any object belonging to that context inside the -performBlock: method.
Your code above is illegal since you're passing those objects back to the main queue. The new API helps you in solving this, though: You create one context that's associated with the main queue, i.e. with NSMainQueueConcurrencyType:
// Assume we have these two context (They need to be set up. Assume they are.)
NSManagedObjectContext *mainMOC = [[[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType] autorelease];
NSManagedObjectContext *backgroundMOC = [[[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType] autorelease];
// Now this can safely be called from ANY thread:
[backgroundMOC performBlock:^{
NSArray *results = [backgroundMOC executeFetchRequest:request error:nil];
for (NSManagedObject *mo in results) {
NSManagedObjectID *moid = [mo objectID];
[mainMOC performBlock:^{
NSManagedObject *mainMO = [mainMOC objectWithID:moid];
// Do stuff with 'mainMO'. Be careful NOT to use 'mo'.
}];
}
}];
This gets less confusing if you move the inner [mainMOC performBlock:] call into its own method. You may also want to pass an array of object IDs back to the main thread's context in stead of executing a block for each object ID. It depends on your needs.
As Daniel Eggert explains, this is definitely still the case. The exception is for NSMainQueueConcurrencyType, where you can also use the managed object context and objects safely on the main thread (as well as from other threads via the performBlock mechanism). The usefulness of this cannot be understated!
iOS 5 also introduced the concept of parent contexts, which also hugely simplify background operations and remove the need to worry about using notifications to propogate changes between threads.
The WWDC 2012 video "Session 214 - Core Data Best Practices" goes into a lot more detail on both subjects and is very comprehensive. The video is essential viewing for anyone using Core Data.

iOS Core Data when to save context?

I'm having random crashes with core data due to concurrency and multithreading. I know that core data is not thread safe. I also found a couple other answers on how to create a ThreadedDataService and instantiate separate context for each thread.
This is a bit too much for me to swallow at the moment, so I'm trying to find an easier way out.
The solution that I'm trying at the moment is simple: saving data through the main thread. However, now a new issue arises: deadlock. The app becomes unresponsive because each of my insertions of a new NSManagedObject is followed by a call to save. (that's my best guess).
Reading the App Delegate documentation, I noticed that it advises me to save context in applicationWillTerminate.
My question is this: For a long running operation that inserts new events every minute, and the user is not required to see updates propagated to all controllers immediately, when is it a good time for me to save the context?
I'm getting a feeling that saving context for each record may be an overkill?
-(void)insertNewEvent
{
// Create a new instance of the entity managed by the fetched results controller.
NSManagedObjectContext *context = [self.fetchedResultsController.managedObjectContext];
NSEntityDescription *entity = [[self.fetchedResultsControllerfetchRequest] entity];
Event*newManagedObject = (Event*)[NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:context];
//use accessor methods to set default values
// Save the context. > IS THIS REALLY NEEDED HERE?
NSError *error = nil;
if (![context save:&error])
{
}else
{
if(newManagedObject!=nil)
{
currentState= newManagedObject;
[currentRecord addEvent:newManagedObject];
//Is this call needed?
[self saveApplicationRecords];
}
}
}
I have methods like these defined for all of my managed objects, is it enough if I call such method on a main thread every 10-15 minutes to save pending changes, rather than doing so after each record insertion?
-(void)saveApplicationRecords
{
NSLog(#"saveApplicationRecords");
NSManagedObjectContext *context = [self.applicationRecordsController.managedObjectContext];
// Save the context.
NSError *error = nil;
if (![context save:&error])
{
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
}
}
An extra question after reading macbirdie's response: is this kind of method legal in core data?
-(Event*)insertAndReturnNewEventWithDate:(NSDate*)date_ type:(int)type
{
NSManagedObjectContext *context = [self.dreamEventsController managedObjectContext];
NSEntityDescription *entity = [[self.dreamEventsController fetchRequest] entity];
DreamEvent *newManagedObject = (Event*)[NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:context];
//handle properties
NSError *error = nil;
if (![context save:&error])
{
return nil;
}else
{
return newManagedObject ;
}
}
Thank you!
You don't have to save the context that early in the process, especially when you want to modify the object afterwards.
In most cases you should create a separate NSManagedObjectContext for the changes you're about to perform on the database. So create objects on it, fill out the properties needed, then send save and perform the whole mergeChangesFromContextDidSaveNotification: trick with the main context (most likely running on the main thread, so using performSelectorOnMainThread... message).
By default an object created and returned by NSManagedObjectContext is autoreleased. If you've created a new object and want to edit it in a form sheet for example, you can call setRetainsRegisteredObjects: with YES to the managed object context before creating the object, so it holds on to the object created until you're done with it. Remember that it's not recommended to manage the NSManagedObjects' lifecycle on your own - you should let the NSManagedObjectContext do it. So, having that in mind, you don't have to retain the NSManagedObject. After the save operation it's unregistered by the context and removed from memory. Nothing is required on your side.
Answer to updated question part
It would be probably better if you returned an NSManagedObjectID (using [object objectID]) instead of the object itself. It allows to safely dispose of the object by the context and if one needs the object for further editing or data retrieval (e.g. from other contexts), they can fetch it from the store separately.
Even if you don't save the context, the newly created object is there and then you can decide if you want to keep the object or not.
After saving on the other hand, if the context still exists, it may return the object with given NSManagedObjectID to you from memory cache - without touching the database.
On yet another hand ;), in your case, you can pretty much safely return the object, since the NSManagedObjectContext creating it still exists, so the object will still exist as well, although already in the autorelease pool.

How to make changes to Core Data objects from different threads without having to save after every change

I have perused all related threads on SO but still confused about how to make changes to core data objects from multiple threads without having to save after every change.
I am working on an app that talks to the server constantly. The app uses Core Data for storage and NSFetchedResultsController is used in a few view controllers to fetch data from the persistence store. Usually when the user performs an action, a network request will be triggered. Before the network request is sent, usually some changes should be made to relevant Core Data objects, and upon server response, more changes would be made to those Core Data objects.
Initially all the Core Data operations were done on the main thread in the same NSManagedObjectContext. It was all well except that when network traffic is high, the app can become unresponsive for several seconds. Obviously that's not acceptable so I looked into moving some Core Data operations to run in the background.
The first approach I tried was to create an NSOperation object to process each network response. Inside the main method of the NSOperation object, I set up a dedicated MOC, make some changes, and commit the changes at the end.
- (void)main
{
#try {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// Create a dedicated MOC for this NSOperation
NSManagedObjectContext * context = [[NSManagedObjectContext alloc] init];
[context setPersistentStoreCoordinator:[APP_DELEGATE persistentStoreCoordinator]];
// Make change to Core Data objects
// ...
// Commit the changes
NSError *error = nil;
if ([context hasChanges] && ![context save:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
}
// Release the MOC
[context release];
// Drain the pool
[pool drain];
}
#catch (NSException *exception) {
// Important that we don't rethrow exception here
NSLog(#"Exception: %#", exception);
}
}
The MOC on the main thread is registered for the NSManagedObjectContextDidSaveNotification.
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(backgroundContextDidSave:)
name:NSManagedObjectContextDidSaveNotification
object:nil];
So when the background context commits changes, the main MOC will be notified and then will merge in the changes:
- (void)backgroundContextDidSave:(NSNotification *)notification
{
// Make sure we're on the main thread when updating the main context
if (![NSThread isMainThread]) {
[self performSelectorOnMainThread:#selector(backgroundContextDidSave:) withObject:notification waitUntilDone:NO];
return;
}
// Merge the changes into the main context
[[self managedObjectContext] mergeChangesFromContextDidSaveNotification:notification];
}
However, as I mentioned earlier, I also need to make changes to Core Data objects from the main MOC. Each change is usually very small (e.g. update one instance variable in an object) but there can be many of them. So I really don't want to save the main MOC after every single change. But if I don't do that, I run into problems when merging changes from background MOC to the main MOC. Merge conflicts occur since both MOCs have unsaved changes. Setting the merge policy doesn't help either, since I want to keep changes from both MOCs.
One possibility is to register the background MOC with the NSManagedObjectContextDidSaveNotification as well, but that approach smells like bad design for me. And I would still need to save the main MOC after every single change.
The second approach I tried was to do all Core Data changes from a dedicated background context running on a permanent background thread.
- (NSThread *)backgroundThread
{
if (backgroundThread_ == nil) {
backgroundThread_ = [[NSThread alloc] initWithTarget:self selector:#selector(backgroundThreadMain) object:nil];
// Actually start the thread
[backgroundThread_ start];
}
return backgroundThread_;
}
// Entry point of the background thread
- (void)backgroundThreadMain
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
// We can't run the runloop unless it has an associated input source or a timer, so we'll just create a timer that will never fire.
[NSTimer scheduledTimerWithTimeInterval:DBL_MAX target:self selector:#selector(ignore) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] run];
// Create a dedicated NSManagedObjectContext for this thread.
backgroundContext_ = [[NSManagedObjectContext alloc] init];
[backgroundContext_ setPersistentStoreCoordinator:[self persistentStoreCoordinator]];
[pool drain];
}
So whenever I need to make Core Data changes from the main thread, I have to get the objectID from the main thread, and pass to the background thread to perform the change. When the background context saves, the changes will then be merged back to the main MOC.
- (void)addProduct:(Product *)product toCatalog:(Catalog *)catalog;
would change to:
- (void)addProduct:(NSManagedObjectID *)productObjectId toCatalog:(NSManagedObjectID *)catalogObjectId
{
NSArray * params = [NSArray dictionaryWithObjects:productObjectId, catalogObjectId, nil];
[self performSelector:(addProductToCatalogInBackground:) onThread:backgroundThread_ withObject:params waitUntilDone:NO];
}
But this seems so convoluted and ugly. Writing code like this seems to negate the usefulness of using Core Data in the first place. Also, I would still have to save the MOC after every single change since I can't get objectId for a new object without saving it to the datastore first.
I feel that I am missing something here. I really hope someone can shed some light on this. Thanks.
A NSManagedObjectContext is just a scratch pad. The act of saving it moves the changes in one scratch pad down to the NSPersistentStoreCoordinator and potentially down to disk. The only way one MOC can know about the changes from another MOC is through the NSPersistentStoreCoordinator. Therefore the save is required. However, saving is a LOT less expensive in the next release of iOS.
If you must be iOS4 or less compliant then saving is the only option. However you can load up the saves and do them less frequently depending on the design of your application. If you are importing data then save when the import is complete or in logical units inside of the import. There is no need to save after every entry, that is wasteful.
BTW, I would suggest using NSOperation instances instead of working with NSThread instances directly. They are easier to work with and will perform better.
Also, you do not need to wrap Objective-C code in try/catch blocks. Very few things will throw an exception; especially on iOS.
Lastly, I would suggest taking a look at my post on CIMGF about importing on a background thread.

Cryptic error from Core Data: NSInvalidArgumentException, reason: referenceData64 only defined for abstract class

I'm doing an iPhone app that reads data from XML file, turn them into Core Data Managed Objects and save them.
The application is working fine, mostly, on smaller data set/XML that contains ~150 objects. I said mostly because 10% of the time, I'd get the following exception from CoreData while trying to save the context:
* Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '* -_referenceData64 only defined for abstract class. Define -[NSTemporaryObjectID_default _referenceData64]!'
On a bigger data set (~2000), this happens every time, but not on the same place. It could fail on the 137th record, 580th, or the very last one. I've tried moving the save point (per object, per 10 objects, save once all objects are alloc/init'ed) but I always hit the exception above.
I've googled the exception and saw someone having the same issues but didn't see any resolutions.
My next step was going to be simplifying the managed objects and relationships to a point where this error stops and build from there to isolate the issue. The last resort is to ditch Core Data and just directly store into sqllite.
Thanks for all your help!
I have the same issue. It works for smaller data sets, but for larger sets I get "_referenceData64 only defined for abstract class" errors. There's no abstract entities in my model.
EDIT:
I think I got this resolved. The issue in my case was a confusion on my part re threads. Here's the guidelines I followed to fix it:
I parse XML data in a thread. On launching said thread, create a new NSManagedObjectContext using the same persistent store coordinator as your main thread's NSManagedObjectContext.
Any new objects you make in your thread should be made for the thread's NSManagedObjectContext. If you have to copy over objects from the main thread's NSManagedObjectContext, copy over by ID. I.e.
NSManagedObjectID *objectID = [foo objectID];
FooClass *newFoo = [(FooClass*)[threadManagedObjectContext objectWithID:objectID] retain]
When finished parsing, you need to save the changes made to the thread's NSManagedObjectContext. You must lock the persistent store coordinator. I used the following (incomplete code):
`
- (void)onFinishParsing {
// lock the store we share with main thread's context
[persistentStoreCoordinator lock];
// save any changes, observe it so we can trigger merge with the actual context
#try {
[threadManagedObjectContext processPendingChanges];
}
#catch (NSException * e) {
DLog(#"%#", [e description]);
[persistentStoreCoordinator unlock];
}
#finally {
// pass
}
NSNotificationCenter *dnc = [NSNotificationCenter defaultCenter];
[dnc addObserver:self selector:#selector(threadControllerContextDidSave:) name:NSManagedObjectContextDidSaveNotification object:threadManagedObjectContext];
#try {
NSError *error;
if (![threadManagedObjectContext save:&error]) {
DLog(#"%#", [error localizedDescription]);
[persistentStoreCoordinator unlock];
[self performSelectorOnMainThread:#selector(handleSaveError:) withObject:nil waitUntilDone:NO];
}
} #catch (NSException *e) {
DLog(#"%#", [e description]);
[persistentStoreCoordinator unlock];
} #finally {
// pass
}
[dnc removeObserver:self name:NSManagedObjectContextDidSaveNotification object:threadManagedObjectContext];
[self performSelectorOnMainThread:#selector(parserFinished:) withObject:nil waitUntilDone:NO];
}
// Merging changes causes the fetched results controller to update its results
- (void)threadControllerContextDidSave:(NSNotification*)saveNotification {
// need to unlock before we let main thread merge
[persistentStoreCoordinator unlock];
[self performSelectorOnMainThread:#selector(mergeToMainContext:) withObject:saveNotification waitUntilDone:YES];
}
- (void)mergeToMainContext:(NSNotification*)saveNotification {
NSError *error;
[managedObjectContext mergeChangesFromContextDidSaveNotification:saveNotification];
if (![managedObjectContext save:&error]) {
DLog(#"%#", [error localizedDescription]);
[self handleSaveError:nil];
}
}
`
You have to follow the rule:
NSManagedObjectContext must be created on the same thread which uses
it. (OR in other words, every Thread must have its own MOC)
Violation of above rule cause the following exception:
Exception in *** -_referenceData64 only defined for abstract class. Define -[NSTemporaryObjectID_default _referenceData64]!,
Another problem somebody might face is, if using the NSFetchedResultsController, the delegates will not be called on the UI classes.
I hope this answer will help someone!
Short Answer:
you must use the following functions while saving context to ensure block operations are executed on the queue specified for the context.
perform(_:)
performAndWait(_:)
as Mentioned in the Apple Documentation of NSManagedObject Concurrency section
here
Long Answer:
As Mentioned in Apple Documentation
Core Data uses thread (or serialized queue) confinement to protect
managed objects and managed object contexts (see Core Data Programming
Guide). A consequence of this is that a context assumes the default
owner is the thread or queue that allocated it—this is determined by
the thread that calls its init method. You should not, therefore,
initialize a context on one thread then pass it to a different thread.
Instead, you should pass a reference to a persistent store coordinator
and have the receiving thread/queue create a new context derived from
that.
From this we can assume
Thread which create NSManagedObject will own that object
you can not create NSManagedObject in one thread and save it in an
other object(cause of error)
either create Separate NSManagedObject for each thread or pass it by
id as answered by Adriaan.
This will arise a new question in you mind
Is there a way to figure out what thread an NSManagedObjectContext is
on?
which is already answered here
and Tom Harrington answer will prove every thing above a little bit inaccurate(I also believe that) and redirect us back to the short answer:)
Do your mapping of nsmanagedobject data and saving of managedobjectcontext in the following block so it locks the managedobjectcontext from accessing by another thread and resolved the crash.
[context performBlockAndWait:^{
//your code
[context save:&error];
}];
sorry for my english (i'm french).
I did have the same issue and I realized that I called a method on Core Data Framework (inserting an object) from a second thread. I just call this method from the Main Thread using performSelectorOnMainThread and it resolve my problem.
I hope it will help you.
Thanks everyone, I was able to get rid of the pesky exception by following your tips.
It was the threading issue that seemed to cause the exception. In my case, I had the main thread spawning worker threads that fetch the XML, parse, create the necessary managed object, and save them.
I tried a lazy way out by using performSelectorOnMainThread for saving but that didn't work.
My final approach was to create a class called ThreadDataService with it's own ManagedObjectContext and each thread has one instance of ThreadDataService, basically what Adriaan had suggested.
Again, thanks for all the answers. You guys rock!
I had the same issue and when searching for an answer I found this. My problem was that I started 2 threads which worked on the same managed context is crashed when saving to the persistent store- if each thread has it own context the issue do not arise. But it might has been resolved by just locking the persistent store, but I believe the 2 managed contexts is the right solution.
Regards