AppsDev
AppsDev

Reputation: 12499

Saving context concurrently in Core Data not working in iOS7

I get some data from a couple of web services that are called asynchronously. When I receive their responses, I need to create and save corresponding entities in Core Data with the information received. Since the services callbacks ara asynchronous, and I could be already saving the response of one of the services when I receive the another, I wrote a couple of methods like this:

- (void)createEntity
{
   @autoreleasepool {
       dispatch_queue_t queue = dispatch_queue_create(kSaveQueue, NULL);
       dispatch_async(queue, ^{
        // Context for background operations
        NSManagedObjectContext *tmpContext = [[NSManagedObjectContext alloc] init];
        NSPersistentStoreCoordinator *mainThreadContextPSC = [self.context persistentStoreCoordinator];
        [tmpContext setPersistentStoreCoordinator:mainThreadContextPSC];

        @try {
           // Parse service response and create entity

           // Save context
           [tmpContext save:nil];

           dispatch_async(dispatch_get_main_queue(), ^{
              // Notify end of operation
           });
        }
        @catch (NSException *ex) {
           NSLog(@"exception: %@", [ex description]);
        }
     });
   }
}

Actually, I have two methods like this, one for let's say EntityA, and another for EntityB, and each one is called when I receive the corresponding service response (serviceA, serviceB). In my tests I see that both tmpContext are always saved in iOS 8, but in iOS 7 it is only the first called which is saved, and the second entity is not persisted in Core Data.

Why does this work in iOS 8 but it doesn't in iOS 7?

Thanks in advance

Upvotes: 0

Views: 272

Answers (2)

andykkt
andykkt

Reputation: 1706

Above answer from Mundi is right and good explanations.. I can give you the code I use to create a thread context and save and stop context

+ (NSManagedObjectContext*)startThreadContext {
    AppDelegate *theDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
    NSManagedObjectContext *moc = theDelegate.managedObjectContext;

    NSThread *thread = [NSThread currentThread];
    if ([thread isMainThread]) {
        return moc;
    }

    // get thread dictionary
    NSMutableDictionary *threadDictionary = [[NSThread currentThread] threadDictionary];
    if ( [threadDictionary objectForKey:@"managedObjectContext"] == nil ) {
        // create a context for this thread
        NSManagedObjectContext *newMoc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
        [newMoc setPersistentStoreCoordinator:[theDelegate persistentStoreCoordinator]];

        // Register for context save changes notification
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(mergeChanges:)
                                                     name:NSManagedObjectContextDidSaveNotification
                                                   object:newMoc];

        [newMoc setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];
        [newMoc processPendingChanges];  // flush operations for which you want undos
        [[newMoc undoManager] disableUndoRegistration];
        newMoc.undoManager = nil;

        // cache the context for this thread
        [threadDictionary setObject:newMoc forKey:@"managedObjectContext"];
    }

    return [threadDictionary objectForKey:@"managedObjectContext"];
}

+ (void)saveAndStopThreadContext:(NSManagedObjectContext *)context {
    // save managed object
    NSError* error = nil;
    BOOL success = [context save:&error];
    if ( !success ) {
        ERRLOG(@"[stopThreadContext] failed to save managedObjectContext (err:%@)", error );
    }

    [[NSNotificationCenter defaultCenter] removeObserver:self
                                                    name:NSManagedObjectContextDidSaveNotification
                                                  object:context];

    NSThread *thread = [NSThread currentThread];
    if (![thread isMainThread]) {
        NSMutableDictionary *threadDictionary = [[NSThread currentThread] threadDictionary];
        [threadDictionary removeObjectForKey:@"managedObjectContext"];
    }
}

And you can use it like this

// get managed object context
NSManagedObjectContext* moc = [CoreDataHelper startThreadContext];
// perform update
[moc performBlock:^{

    /*
    Do something... 
    */

    // save and stop thread context
    [CoreDataHelper saveAndStopThreadContext:moc];
}];

Upvotes: 0

Mundi
Mundi

Reputation: 80265

Your approach to create context with alloc init and then assign the persistent store coordinator is deprecated.

Instead, use the factory method initWithConcurrencyType: and pass NSPrivateQueueConcurrencyType for a background thread. Associate with the parent context by calling setParentContext:.

You can also do background operations by taking advantage of the context's performBlock and performBlockAndWait APIs rather than dropping down to GCD.

Upvotes: 1

Related Questions