I am new to Core Data and I have looked through many Cored Data related questions, but I cannot really grasp perfectly the whole concept of Core Data and concurrency. It seems like there was one way of doing it, but it got changed in the recent years and now it's a bit hard to filter what information is up to date and what is not.
I am working on a small app, which imports shops data from a web service, presents it to a user and monitors when the user enters the region of a particular shop.
I am using two NSManagedObjectContext
's, which are part of CoreDataHelper
class. This is the init
of the CoreDataHelper
implemented as singleton:
- (id)init {
self = [super init];
if (!self) {return nil;}
_model = [NSManagedObjectModel mergedModelFromBundles:nil];
_coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:_model];
_parentContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[_parentContext performBlockAndWait:^{
[_parentContext setPersistentStoreCoordinator:_coordinator];
[_parentContext setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];
}];
_context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[_context setParentContext:_parentContext];
[_context setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];
return self;
}
I use the _context
for presenting the data to the user and the _parentContext
for importing and synchronising data on the background.
My problem is when a user enters region and didEnterRegion
is called I use the following method to retrieve the NSManagedObject
for the shop in this region
- (Shop*)findShopWithID:(NSString*)shopID
{
NSArray* fetchedObjects;
NSManagedObjectContext* context = [self getBackgroundManagedObectContext];
if(context==nil)
[self sendRemoteLog:@"Context is nil"];
NSFetchRequest* fetch = [[NSFetchRequest alloc] init];
NSEntityDescription* entityDescription = [NSEntityDescription entityForName:@"Shop" inManagedObjectContext:context];
[fetch setEntity:entityDescription];
[fetch setPredicate:[NSPredicate predicateWithFormat:@"shopID==%@", shopID]];
NSError* error = nil;
fetchedObjects = [context executeFetchRequest:fetch error:&error];
if ([fetchedObjects count] == 1)
return [fetchedObjects objectAtIndex:0];
else
{
[self sendRemoteLog:[NSString stringWithFormat:@"No object found for ID %@",shopID]];
return nil;
}
}
- (NSManagedObjectContext*)getBackgroundManagedObectContext
{
CoreDataHelper* cdh = [(AppDelegate*)[[UIApplication sharedApplication] delegate] cdh];
return [cdh backgroundSaveContext];
}
However sometimes, not always, the fetch request does not find the shop with the given ID, even though it is 100% there. The fact that this problem occurs only sometimes leads me to the conclusion that this has something to do with concurrency. Additionally, I believe that this may be related to the fact that I use the same parentContext
object here for performing the fetch and also for importing the data from the web service asynchronously. I should probably change it, but I think this is not the only problem.
EDIT: Here is saving mechanism in the CoreDataHelper
- (void)saveContext
{
if ([_context hasChanges]) {
NSError* error = nil;
if ([_context save:&error]) {
NSLog(@"_context SAVED changes to persistent store");
}
else {
NSLog(@"Failed to save _context: %@", error);
}
}
else {
NSLog(@"SKIPPED _context save, there are no changes!");
}
}
- (void)backgroundSaveContext {
// First, save the child context in the foreground (fast, all in memory)
[self saveContext];
// Then, save the parent context.
[_parentContext performBlock:^{
if ([_parentContext hasChanges]) {
NSError *error = nil;
if ([_parentContext save:&error]) {
NSLog(@"_parentContext SAVED changes to persistent store");
}
else {
NSLog(@"_parentContext FAILED to save: %@", error);
}
}
else {
NSLog(@"_parentContext SKIPPED saving as there are no changes");
}
}];
}
After lots of debugging and patience I managed to pinpoint the problem. It didn't have anything to do with concurrency and was a silly mistake in my predicate. I was using the following line of code:
[fetch setPredicate:[NSPredicate predicateWithFormat:@"shopID==%@", shopID]];
I was wrongly using the shopID as NSString
while in my core data model and NSManagedObject
it was a NSNumber
. I reconsidered an changed the shopID to be stored as NSString
and now it works perfectly fine. However I still can not explain why it was working sometimes and sometimes not...