Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

CoreData Background Thread Update Has Random EXC_BAD_ACCESS KERN_INVALID_ADDRESS Error

I have a random bug that has plagued me for months that I simply can't figure out. I would say that it fails fewer than 1 in a 1000 times. I must have CoreData configured incorrectly but I can't figure out or recreate it. The basic gist is that I receive some information from the server and I am then updating a CoreData object in a background thread. The CoreData object is not immediately needed for the UI.

All of this is performed in DataService which has a reference to the NSManagedObjectContext that was originally created in the AppDelegate. Note: Anything that references [DataService sharedService] uses the AppDelegate.NSManagedObjectContext:

@interface DataService : NSObject {}
  @property (nonatomic,strong) NSManagedObjectContext* managedObjectContext;
@end

When the server returns with data the updateProduct method is called:

@implementation DataService

  + (NSManagedObjectContext*) newObjectContext
  {
    NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];           //step 1

    AppDelegate* appDelegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];

    [context setPersistentStoreCoordinator:appDelegate.persistentStoreCoordinator];
    [context setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];               
    [appDelegate.managedObjectContext observeContext:context];

    return context;
  }

  +(void) saveContext:(NSManagedObjectContext*) context
  {
    NSError *error = nil;
    if (context != nil) {
      if ([context hasChanges] && ![context save:&error]) {
        // Handle Error
      }
    }
  }

  +(void) updateProduct: (Product*) product
  {
    if(product == nil)
      return;

    //run in background
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, (unsigned long)NULL), ^(void){

      //create private managed object
      NSManagedObjectContext *context = [DataService newObjectContext];

      CoreDataProduct* coreProduct = [DataService product:product.productId withObjectContext:context];
      if(product != nil)
      {
        //copy data over from product
        coreProduct.text = product.text;

        //ERROR HAPPENS HERE on save changes
        [DataService saveContext:context];
      }

    //remove background context listening from main thread
    [DataService.managedObjectContext stopObservingContext:context];

});
  }

@end

I use the general NSManagedObjectContext+Helper.h category file that is floating around GitHub and my EXC_BAD_ACCESS KERN_INVALID_ADDRESS error happens in the [DataService.managedObjectContext mergeChangesFromNotification:(NSNotification *)notification] method which calls this

@implementation NSManagedObjectContext (Helper)

  - (void) mergeChangesFromNotification:(NSNotification *)notification
  {
    //ERROR HAPPENS HERE
    [self mergeChangesFromContextDidSaveNotification:notification];
  }

@end

I cannot figure out why the mergeChangesFromContextDidSaveNotification method fails randomly. I think that the error is due to losing reference to the original shared managedObjectContext. Although if that were true, I guess I would expect the error to be in the updateProduct method and not in the category class.

I suppose that both the newObjectContext and the stopObservingContext methods reference the managedObjectContext on the background thread from the main thread. Since I'm creating a private managedObjectContext, do I even need to make the main thread shared context aware of the private context? If so, am I doing it incorrectly?

Thanks in advance for the help.

like image 720
dirkoneill Avatar asked Jul 15 '14 05:07

dirkoneill


1 Answers

It appears because the new NSManagedObjectContext was being created on the background thread, the original/parent NSManagedObjectContext needed to be observed on the main thread. Once I changed observeContext to observeContextOnMainThread, this CoreData problem seems to have gone away. I hope this helps someone.

Here's my updated method:

+ (NSManagedObjectContext*) newObjectContext
  {
    NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];           //step 1

    AppDelegate* appDelegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];

    [context setPersistentStoreCoordinator:appDelegate.persistentStoreCoordinator];
    [context setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];               
    [appDelegate.managedObjectContext observeContextOnMainThread:context];

    return context;
  }
like image 115
dirkoneill Avatar answered Oct 16 '22 20:10

dirkoneill