Peter Warbo
Peter Warbo

Reputation: 11700

Core Data background processing, save is not pushed to main context

I'm exeperiencing some issues with some Core Data background processing. When I save in my background context it does not seem to push the save up to the main context. When debugging the code I noticed from the background thread that is doing the save operation that it seems to be halted(?) This behaviour causes me to fetch outdated objects.

Stacktrace from save:

Thread 29, Queue : NSManagedObjectContext Queue
#0  0x9a5cf80e in semaphore_wait_trap ()
#1  0x02216f08 in _dispatch_thread_semaphore_wait ()
#2  0x02214b3a in _dispatch_barrier_sync_f_slow ()
#3  0x02214a5c in dispatch_barrier_sync_f ()
#4  0x01dfe03b in _perform ()
#5  0x01dfde9e in -[NSManagedObjectContext(_NestedContextSupport) executeRequest:withContext:error:] ()
#6  0x01ddb33c in -[NSManagedObjectContext save:] ()
#7  0x00096213 in __45-[CoreDataHelper saveInManagedObjectContext:]_block_invoke_0 at /Users/peterwarbo/Documents/Projects/MessagePlanr/MessagePlanr/CoreDataHelper.m:307
#8  0x01e734b3 in developerSubmittedBlockToNSManagedObjectContextPerform_privateasync ()

Save method:

- (void)saveInManagedObjectContext:(NSManagedObjectContext *)context {

    if (context == nil) {

        // Use default MOC
        context = self.managedObjectContext;

        NSError *error = nil;

        if (context != nil)
        {
            if ([context hasChanges] && ![context 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.
                 */
                DLog(@"Unresolved error %@, %@", error, [error userInfo]);
                abort();
            }
        }

    } else {

        // First save (child) context
        [context performBlock:^{

            NSError *error = nil;

            if ([context hasChanges] && ![context 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.
                 */
                DLog(@"Unresolved error %@, %@", error, [error userInfo]);
                abort();
            }
        }];


        // Then save parent context
        [self.managedObjectContext performBlock:^{

            NSError *error = nil;

            if ([self.managedObjectContext hasChanges] && ![self.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.
                 */
                DLog(@"Unresolved error %@, %@", error, [error userInfo]);
                abort();
            }
        }];
    }
}

This is the method that saves, Reminder is a NSManagedObject, when operation is complete I call a completion block. However in the completion block when fetching some NSManagedObjects they have not been updated (due to the save halting I presume?)

- (void)checkOverdueRemindersInBackgroundWithCompletionBlock:(void (^)(NSInteger overdueCount, NSArray *reminders))block {

    DLogName()

    // Creating a new MOC for thread safety
    NSManagedObjectContext *syncContext = [self threadedManagedObjectContext];

    [syncContext performBlock:^{

        NSArray *reminders = [self fetchEntity:APReminderEntity predicate:nil andSortDescriptors:nil inManagedObjectContext:syncContext];

        NSInteger overdueCount = 0;

        for (Reminder *reminder in reminders) {

            [reminder checkOverdue]; // Checks if object is overdue and sets a flag if it is

            [self saveInManagedObjectContext:syncContext];

            if (reminder.status.intValue == RMReminderStatusOverdue) {

                overdueCount++;
            }

        }

        block(overdueCount, reminders);
    }];
}

threadedManagedObjectContext method:

- (NSManagedObjectContext *)threadedManagedObjectContext {

    NSManagedObjectContext *threadedMoc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
    threadedMoc.parentContext = self.managedObjectContext; //self.managedObjectContext is of type NSMainQueueConcurrencyType

    return threadedMoc;
}

Upvotes: 1

Views: 834

Answers (1)

Peter Warbo
Peter Warbo

Reputation: 11700

The issue is arisen because I'm saving asynchronously using performBlock: which returns immediately thus when it is returned and my completion block is called the save might not be committed.

So the answer to this issue is to run the background saving process within performBlockAndWait:

Now another question has arisen, are there any downsides using performBlockAndWait: ?

Upvotes: 1

Related Questions