Search code examples
iphonecore-datansmanagedobjectcontextnsoperation

SEGV_ACCERR and crash app sometime on NSOperation when do [self.managedObjectContext save:&error]


I have a problem that I can't understand how to handle because does not happen logically.

I have some NSOperations that run concurrently. For example,

- (void)main
{
    @autoreleasepool
    {        
        AppDelegate *appController = (AppDelegate *)[[UIApplication sharedApplication] delegate];

        self.managedObjectContext = [[NSManagedObjectContext alloc] init];
        [self.managedObjectContext setUndoManager:nil];
        [self.managedObjectContext setPersistentStoreCoordinator: [appController persistentStoreCoordinator]];

        NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; 
        [nc addObserver:self
               selector:@selector(mergeChanges:) 
                   name:NSManagedObjectContextDidSaveNotification
                 object:self.managedObjectContext];
        NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
        NSEntityDescription *entity = [NSEntityDescription 
                                       entityForName:@"Entity" inManagedObjectContext:self.managedObjectContext];
        [fetchRequest setEntity:entity];

        [fetchRequest setPredicate:[NSPredicate predicateWithFormat:@"%K != %@",@"number1",[NSNumber numberWithInt:2]]];

        NSError *error;
        NSArray *fetchedObjects = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];

        for (NSManagedObject *obj in fetchedObjects) {

            //Do Something with managed object then save        
            NSError *error = nil;
            //[episode release];
            if (![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. If it is not possible to recover from the error, display an alert
                // panel that instructs the user to quit the application by pressing the Home button.
                //
                NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
                abort();
            }
        }
    }
}

- (void)mergeChanges:(NSNotification *)notification
{
    AppDelegate *appController = (AppDelegate *)[[UIApplication sharedApplication] delegate];
    NSManagedObjectContext *mainContext = [appController managedObjectContext];

    // Merge changes into the main context on the main thread
    [mainContext performSelectorOnMainThread:@selector(mergeChangesFromContextDidSaveNotification:) 
                              withObject:notification
                           waitUntilDone:YES];  
} 

This is my typical NSOperation, that works concurrently and update my object in the core data, and sometime with no explanation the app crash and I receive that error on this line:

if (![self.managedObjectContext save:&error])

In my crash reports, so my question is, there is a way to prevent the app crash and fix the error? Can I can use a @syncronized when I perform save? Is this due to different threads and different objects? How can I fix this?


Solution

  • Move the code you use to merge changes in the app delegate.

    So, within application:didFinishLaunchingWithOptions: register for the notification.

    NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; 
    [nc addObserver:self
           selector:@selector(mergeChanges:) 
               name:NSManagedObjectContextDidSaveNotification
             object:nil]; // set nil here
    

    Then, always within the app delegate create your mergesChanges: method. Here you need to be sure that the notification runs on the main thread and the context you received the notification is different from the main one.

    - (void)mergeChanges:(NSNotification *)notification
    {
        if ([notification object] == [self managedObjectContext])
            return;
    
        if (![NSThread isMainThread]) {
            [self performSelectorOnMainThread:@selector(mergeChanges:) withObject:notification waitUntilDone:YES];
            return;
        }
    
        NSManagedObjectContext *mainContext = [self managedObjectContext];
    
        // Merge changes into the main context on the main thread
        [mainContext performSelectorOnMainThread:@selector(mergeChangesFromContextDidSaveNotification:) 
                                  withObject:notification
                               waitUntilDone:YES];  
    }
    

    P.S. Here you are using a non-concurrent NSOperation that, if inserted into a NSOperationQueue, will run in a concurrent manner (the queue, by means of GCD will manage this for you).