Initialising NSManagedObjectContext on a serial NSOperationQueue - core-data

I have an NSOperationQueue that has some NSBlockOperations added to it, among which are blockOperations A and B. The NSOperationQueue has a maxConcurrencyOperationCount of 1.
blockOperation B, is dependant on A being finished. In each of the blockOperations I am calling a method, which in turn calls another method, that initialises a new NSManagedObjectContext (with the persistentStoreCoordinator from a singleton), that I use to create and add objects to a Core Data database. The code invoked by the aforementioned second method call in A and B looks like this (it varies slightly for for each of them):
NSManagedObjectContext *managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
managedObjectContext.persistentStoreCoordinator = [[CoreDataController sharedCoreDataController] persistantStoreCoordinator];
for (NSDictionary *articleDictionary in array) {
if (![Article articleExistsWithIDInDictionary:articleDictionary inContext:managedObjectContext]) {
[Article articleFromDictionary:articleDictionary inContext:managedObjectContext];
}
}
[[CoreDataController sharedCoreDataController] saveContext:managedObjectContext];
// method ends.
The saveContext: code looks like this:
NSError *error = nil;
if (managedObjectContext != nil) {
if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
}
}
Having spent a lot of time reading Apples Docs about Core Data Concurrency, NSOperation, etc., I'm still unsure if what I'm doing with NSManagedObjectContext is thread-safe, and generally considered to be OK? Some clarification and/or indication of that I should be doing differently would be much appreciated. If you need to see any more code, please ask.
Thanks in advance.

What you are doing is NOT thread safe.
If you decide to create a context per operation, you better use the confinement concurrency type:
context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];
this way you don't need to change anything in your current code.
if you want to use the context with NSPrivateQueueConcurrencyType you must access objects within that context by the performBlock: or performBlockAndWait::
[managedObjectContext performBlockAndWait:^{//wait is used as to not end the operation before this code is executed
for (NSDictionary *articleDictionary in array) {
if (![Article articleExistsWithIDInDictionary:articleDictionary
inContext:managedObjectContext])
{
[Article articleFromDictionary:articleDictionary
inContext:managedObjectContext];
}
}
}];
I would probably go with my first solution in your case.
all that said, you could simply use a "private queue" context as a serial queue (as long as you add block operation to it in the order you need them to execute).
A context performBlock: method will queue the block and execute it serially in regard to other blocks added that context for execution in the background:
//add this to your CoreDataController
context = [[CoreDataController sharedCoreDataController] serialExecutionBGContext];
[context performBlock:^{ //your block operation code1}];
[context performBlock:^{ //your block operation code2}];
this will perform code1 and code2 in the background serially.
In this manner, you save the overhead of allocating a new context, and might benefit from caching done by this context.
You might want to reset this context every now and then so it will not get bloated with fetched objects.

The concern with the context is that it be accessed only within a single thread. Setting the MaxConcurrencyOperationCount does not guarantee that. Another approach is to make the context a "thread" variable, storing a context in each thread dictionary where it is used.
Ex:
+ (NSManagedObjectContext*)managedObjectContext
{
NSMutableDictionary *threadDictionary = [[NSThread currentThread] threadDictionary];
NSManagedObjectContext *context = [threadDictionary valueForKey:#"QpyManagedObjectContext"];
if (context == nil) {
#autoreleasepool {
context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];
[context setStalenessInterval: 10.0];
[context setMergePolicy:[[NSMergePolicy alloc]initWithMergeType:NSOverwriteMergePolicyType]];
NSPersistentStoreCoordinator *coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[Qpyd managedObjectModel]];
[context setPersistentStoreCoordinator:coordinator];
NSString *STORE_TYPE = NSSQLiteStoreType;
NSString *path = [[NSProcessInfo processInfo] arguments][0];
path = [path stringByDeletingPathExtension];
NSURL *url = [NSURL fileURLWithPath:[path stringByAppendingPathExtension:#"sqlite"]];
NSError *error;
NSPersistentStore *newStore = [coordinator addPersistentStoreWithType:STORE_TYPE configuration:nil URL:url options:nil error:&error];
if (newStore == nil) {
NSLog(#"Store Configuration Failure %#", ([error localizedDescription] != nil) ? [error localizedDescription] : #"Unknown Error");
}
[threadDictionary setObject:context forKey:#"QpyManagedObjectContext"];
}
}
return context;
}

Related

NSManagedObjectContext - Child Context causing deadlock

I have a parent - child - grandchild core data context setup in Core Data as below. Whenever I try execute a fetch request on the grandchild context, it causes a deadlock on the thread
- (NSManagedObjectContext *)defaultPrivateQueueContext
{
if (!_defaultPrivateQueueContext) {
_defaultPrivateQueueContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
_defaultPrivateQueueContext.persistentStoreCoordinator = self.persistentStoreCoordinator;
}
return _defaultPrivateQueueContext;
}
- (NSManagedObjectContext *)mainThreadContext {
if (!_mainThreadContext) {
_mainThreadContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
_mainThreadContext.parentContext = [self defaultPrivateQueueContext];
}
return _mainThreadContext;
}
+ (NSManagedObjectContext *)newPrivateQueueContext
{
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
context.parentContext = [[self sharedParliamentAPI] mainThreadContext];
return context;
}
This is my code where it causes a deadlock (when trying to execute a fetch request):
- (void)fetchMenuItemsWithCompletion:(void (^) (BOOL success, NSString *message))completionBlock {
NSMutableURLRequest *request = [APIHelper createNewRequestWithURLExtension:#"menuitems" httpMethodType:#"GET" parameters:nil];
NSURLSession *session = [NSURLSession sessionWithConfiguration:self.sessionConfig];
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSObject *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
if ([[json valueForKey:#"isSuccess"] boolValue]) {
NSManagedObjectContext *defaultContext = self.defaultPrivateQueueContext;
NSManagedObjectContext *privateQueueContext = [ParliamentAPI newPrivateQueueContext];
[privateQueueContext performBlock:^{
__block NSError *error;
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"MenuItem"];
NSArray *fetchedRecords = [privateQueueContext executeFetchRequest:request error:&error];
// do stuff with fetchedRecords
}];
} else {
completionBlock([[json valueForKey:#"isSuccess"] boolValue], [json valueForKey:#"message"]);
}
}];
[dataTask resume];
}
Your Core Data object structure looks like this:
Where you have a NSPrivateQueueConcurrencyType context as the root context connected to the persistent store coordinator, which has a child NSMainQueueConcurrencyType context, which in turn has a NSPrivateQueueConcurrencyType context. This structure is recommended by a lot of people who write on The IntarWebs.
What is happening in your case is that the private queue context that is the main queue context is becoming busy, which is causing it's child to wait. Because the main queue context is using the main queue for all of it's work, it's not necessarily busy doing Core Data work when this happens (though this is still somewhat likely). The main queue does a lot of work other than Core Data, and all of those things will end up impacting the child any time it needs to communicate with the main queue context.
Additionally, a context created with NSMainQueueConcurrencyType is allowed to execute Core Data operations without explicitly using performBlock: or performBlockAndWait:.
For example, a main queue context can do this:
NSArray *results = nil;
NSError *error = nil;
results = [mainQueueContext executeFetchRequest:fetchRequest error:&error];
And is not required to do this:
[mainQueueContext performBlock:^{
NSArray *results = nil;
NSError *error = nil;
results = [mainQueueContext executeFetchRequest:fetchRequest error:&error];
}];
The difference here is that the first example without the performBlock: will block the main thread waiting for the result. The second, using performBlock:, is asynchronous and will not block - the fetch request will be scheduled for execution on the queue. Obviously, the first example can cause some contention in any child contexts.
If your configuration had a NSMainQueueConcurrencyType context that was a child of another NSMainQueueConcurrencyType context, that would be... bad. It's almost guaranteed to deadlock in that configuration. The good news is, you don't have that issue!
You can convert your code to using performBlock: with the NSMainQueueConcurrencyType context to mitigate this part of the problem. You can also use an NSPrivateQueueConcurrencyType in place of your main queue context - there is not much of a good reason to use a main queue context at all. NSFetchedResultsController can be used with a private queue context to do "background fetching".

Core Data read only managed objects on thread not returning result to delegate

I need to use some core data managed objects in an NSOperation. The problem is that core data is not thread safe and apparently the object can't be loaded from the new thread. Does anybody know a good tutorial for this? I need the object read only... so the thread will not modify them in any way. Some other, unrelated entities may be added on the main thread while these objects are used in the background, but the background entities don't need to be modified at all..
Hmm seemed I fixed the background running issue, but now the problem is nothing is returned to the delegate... Why? In the thred if I nslog the results are all shown but that call to the delegate never happens
This is the code:
-(void)evaluateFormula:(Formula *)frm runNo:(NSUInteger)runCount{
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue setMaxConcurrentOperationCount:2];
NSManagedObjectID *formulaId = frm.objectID;
for (int i = 0; i < runCount; i++) {
NSInvocationOperation * op = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(runFormula:) object:formulaId];
[queue addOperation:op];
}
}
-(void)runFormula:(NSManagedObjectID *)fId {
NSManagedObjectContext *thredContext =[[NSManagedObjectContext alloc] init];
NSPersistentStoreCoordinator *coord = (NSPersistentStoreCoordinator *)[(PSAppDelegate *)[[UIApplication sharedApplication] delegate] persistentStoreCoordinator];
[thredContext setPersistentStoreCoordinator:coord];
Formula *f = (Formula *)[thredContext objectWithID:fId];
NSDictionary *vars = [self evaluateVariables:[f.hasVariables allObjects]];
NSMutableString *formula = [NSMutableString stringWithString:f.formula];
for (NSString *var in [vars allKeys]) {
NSNumber *value =[vars objectForKey:var];
[formula replaceOccurrencesOfString:var withString:[value stringValue] options:NSCaseInsensitiveSearch range:NSMakeRange(0, [formula length])];
}
//parse formula
NSNumber *result = [formula numberByEvaluatingString];
// NSLog(#" formula %# result : %d",formula,[result intValue]);
//aggregate results
[self performSelectorOnMainThread:#selector(aggregate:) withObject:result waitUntilDone:YES]; // the delegate doesn't get called ...
}
-(void)aggregate:(NSNumber *)res {
[self.delegate didReceiveResult:res];
}

How to prevent race conditions when merging changes across threads?

A typical setup: we have a main thread with a mainMOC and a background thread with its own backgroundMOC. The background thread performs read-only operations on the backgroundMOC by dispatching blocks to a backgroundQueue.
The backgroundMOC needs to merge the changes from the mainMOC so we register for NSManagedObjectContextDidSaveNotification and then do something like
- (void)mainMocDidSave:(NSNotification *)notification {
dispatch_async(backgroundQueue, ^{
[backgroundMoc mergeChangesFromContextDidSaveNotification:notification];
});
}
Let's say the user deletes an object in the mainMOC. The code above does not seem safe to me, since the merge will be done at some point in the future. Until the merge is done, there might still be blocks on the backgroundQueue that are trying to use the deleted object.
The obvious solution would be to use dispatch_sync instead (or performBlockAndWait, performSelector:OnThread:...) instead. From the code snippets I see on the interwebs, this seems to be what everybody is doing. But I'm not comfortable with this solution either.
The name NSManagedObjectContextDidSaveNotification implies that the save has already happened when the notification is delivered. So the corresponding row has already been deleted from the underlying database (assuming an sqlite store). dispatch_sync will have to wait for other blocks on the queue to finish before it can merge the changes, and these other blocks could still try to work with the deleted object, leading to an NSObjectInaccessibleException.
It seems to me that the correct way to merge changes from one thread/queue to another would be to
Subscribe to NSManagedObjectContextWillSaveNotification and NSManagedObjectContextDidSaveNotification on the background thread.
On NSManagedObjectContextWillSaveNotification: empty the backgroundQueue and suspend any operations that dispatch new blocks to the queue.
On NSManagedObjectContextDidSaveNotification: merge the changes synchronously.
Resume normal operation on the background queue.
Is this the correct approach or am I missing something?
I use the following structure in two projects in which I experienced similar troubles as you did. First of all I use singleton service to ensure there is only one background thread merging and reading changes.
AppDelegate.m
- (NSManagedObjectContext *)managedObjectContext {
if (_managedObjectContext != nil) {
return _managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
// It is crucial to use the correct concurrency type!
_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[_managedObjectContext setPersistentStoreCoordinator:coordinator];
}
return _managedObjectContext;
}
- (void)saveContext {
NSError *error = nil;
NSManagedObjectContext *managedObjectContext = self.managedObjectContext;
if (managedObjectContext != nil) {
if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error]) {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
else {
[[NSNotificationCenter defaultCenter] postNotificationName:#"ParentContextDidSaveNotification" object:nil];
}
}
}
BackgroundService.m
- (id)init {
self = [super init];
if (self) {
[self managedObjectContext];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(parentContextDidSave) name:#"ParentContextDidSaveNotification" object:nil];
}
return self;
}
- (NSManagedObjectContext *)managedObjectContext {
if (!_managedObjectContext) {
// Again, make sure you use the correct concurrency type!
_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[_managedObjectContext setParentContext:[(AppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext]];
}
return _managedObjectContext;
}
- (BOOL)saveContext {
#synchronized(self) {
BOOL successful = YES;
// Bad practice, process errors appropriately.
[[self managedObjectContext] save:nil];
[[[self managedObjectContext] parentContext] performBlock:^{
[(AppDelegate *)[[UIApplication sharedApplication] delegate] saveContext];
}];
return successful;
}
}
- (void)parentContextDidSave {
[[self managedObjectContext] reset];
[[NSNotificationCenter defaultCenter] postNotificationName:#"ManagedObjectContextResetNotification" object:nil];
}

CoreData Fetch Crashes if used with dispatch_async

I have a core-data app that runs without crashing if I perform a fetch inside viewDidLoad like this:
- (void) performCoreDataFetch {
NSError *error;
if (![[self fetchedResultsController] performFetch:&error]) {
exit(-1); // Fail
}
}
- (void)viewDidLoad {
[super viewDidLoad];
[self performCoreDataFetch];
}
The only problem with the above way of performing fetch is if the data to be returned is big, it freezes the app for a few seconds (but does return correct result without crashing every single time), so to avoid that I decided to use dispatch_async (code shown below) and call [self performCoreDataFetch] inside it.
But if I run this same [self performCoreDataFetch] inside dispatch_sync within viewDidLoad, like shown below,:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self performCoreDataFetch];
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
});
Calling [self performCoreDataFetch] within dispatch_async crashes the app randomly saying "-[NSFetchRequest fetchLimit]: message sent to deallocated instance"
My fetchedResultsController method looks like this:
- (NSFetchedResultsController *)fetchedResultsController {
if (fetchedResultsController != nil) {
return fetchedResultsController;
}
// Create and configure a fetch request with the Organization entity
NSFetchRequest *request = [[NSFetchRequest alloc] init];
request.entity = [NSEntityDescription entityForName:#"Organization" inManagedObjectContext:managedObjectContext];
request.fetchBatchSize = 20;
// create sortDescriptor array
NSSortDescriptor *nameDescriptor = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES selector:#selector(caseInsensitiveCompare:)];
NSArray *sortDescriptorArray = [NSArray arrayWithObjects:nameDescriptor, nil];
request.sortDescriptors = sortDescriptorArray;
NSPredicate *predicate = nil;
predicate = [NSPredicate predicateWithFormat:#"state LIKE %#", filterByState];
[request setPredicate:predicate];
// Create and initialize the fetchedResultsController
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc ] initWithFetchRequest:request managedObjectContext:managedObjectContext sectionNameKeyPath:#"uppercaseFirstLetterOfName" cacheName:nil];
self.fetchedResultsController = aFetchedResultsController;
fetchedResultsController.delegate = self;
// Memory management
filterByState = nil;
// [sortDescriptorArray release];
[nameDescriptor release];
// [predicate release];
[request release];
[aFetchedResultsController release];
return fetchedResultsController;
}
Core data is not thread safe if you perform a fetch for a fetchedResultsController. It makes sense, as the fetchedResultsController is the datasource of your UI. Rather than perform a fetch, set your fetchedResultsController to nil and reload your tableView.
Core data is not thread save. To be more exact, the NSManagedObjectContext is not save. All NSManagedObject belong to a particular NSManagedObjectContext and they are not interchangeable.
Pre IOS 5 you need to put really really complicated method. Basically each thread require it's own NSManagedContext
After IOS5, you can do:
__managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
Then you can do
[__managedObjectContext performBlock ^{
//Some really long operation
}]
on any thread that is not main thread.
That will do it on a different thread however in a thread save way. Basically core data will put your operation into queues and it will execute that one by one locking the managedObjectContext for each operation.

Retrieving data while multi threading

I am using multithreading while loading data from the database.
I am doing the following
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
^{
// Get data
NSDate *pastDate = [CommonHelper getSelectedDateYearBackComponents:[NSDate date]];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"users == %# && startDate >= %#", objUser,pastDate];
NSMutableArray *fetchArray = [DataAccess searchObjectsInUserContext:#"userIngo" :predicate :#"startDate" :NO];
if ([fetchArray count] > 0)
{
dispatch_async(dispatch_get_main_queue(),
^{
// Reload table
[self.tableView reloadData]; });
}
else
{
dispatch_async(dispatch_get_main_queue(),
^{ // calling Webservice
});
}
});
where users is the entity from which I am trying to fetch data and objUser is the user object for whom I am retrieving data from the users entity
and my searchObjectsInUserContext code is like this
+(NSMutableArray *) searchObjectsInLabContext: (NSString*) entityName : (NSPredicate *) predicate : (NSString*) sortKey : (BOOL) sortAscending
{
i3EAppDelegate *appDelegate = (i3EAppDelegate *)[[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] init];
[context setUndoManager:nil];
[context setPersistentStoreCoordinator:[appDelegate persistentStoreCoordinator]];
// Register context with the notification center
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self selector:#selector(mergeChanges:) name:NSManagedObjectContextDidSaveNotification object:context];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:entityName inManagedObjectContext:context];
[request setEntity:entity];
[request setFetchBatchSize:10];
[request setReturnsObjectsAsFaults:NO];
// If a predicate was passed, pass it to the query
if(predicate != nil)
{
[request setPredicate:predicate];
}
// If a sort key was passed, use it for sorting.
if(sortKey != nil)
{
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:sortKey ascending:sortAscending];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[request setSortDescriptors:sortDescriptors];
[sortDescriptors release];
[sortDescriptor release];
}
NSError *error;
NSMutableArray *mutableFetchResults = [[context executeFetchRequest:request error:&error] mutableCopy];
if (mutableFetchResults == nil) {
// Handle the error.
// NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
exit(-1); // Fail
}
[request release];
appDelegate = nil;
return [mutableFetchResults autorelease];
}
So in my searchObjectInUserContext I use multiple managedObjectContext so that it does not create problems for me while multithreading as explained by Fred McCann in his blog post.
However, my problem is at NSMutableArray *mutableFetchResults in my searchObjectsInUserContext because it returns 0 at times even though there is data in the database.
Can someone help me with what I am doing wrong?
You are leaking the context you create; you never release it.
There is no need to register as an observer because you are never saving with that context.
Why are you making a mutable copy of the context? That rarely serves any useful purpose.
How do you know there is data in the database?
Is this method being run on a background thread?
Update
There is nothing wrong with your fetch so I suspect the issue might be one of timing. Is this fetch being run before another thread saves? That would explain the behavior you are seeing.
Also, why are you running this on a background thread? Are you seeing a performance issue that requires a background search like this?
Update 2
First, I still question the need for the background fetching. That is normally reserved for when you have performance issues as fetching is very fast.
Second, you fetch on a background queue but then you don't do anything with the data you fetched. You do not hand it off to the table (which would be bad anyway) you just fetch and throw it away.
If you are fetching just to count then don't fetch, do a -[NSManagedObjectContext -countForFetchRequest: error:]. It will be even faster and removes the need for the background queue call.
If you are expecting to do something with the results you have an issue. Instances of NSManagedObject cannot cross a thread/queue boundary. So fetching on the background queue does very little for the UI. You need to fetch those objects on the main queue/main thread. If you don't you will crash because Core Data does not allow it.
In the end, your background queues are doing you more harm than good and they are not solving the problem you are trying to solve.

Resources