0

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?

Lorenzo B
  • 33,216
  • 24
  • 116
  • 190
Piero
  • 9,173
  • 18
  • 90
  • 160

2 Answers2

2

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).

Lorenzo B
  • 33,216
  • 24
  • 116
  • 190
  • thank you for the answers, so i doesn't need to change my nsoperation like @Joel sad? override the start method and not the main method like i did? – Piero Jan 13 '13 at 12:18
  • If you use a `NSOperationQueue`, it's not needed. @Piero take a look also at [importing-and-displaying-large-data-sets-in-core-data](http://www.cimgf.com/2011/08/22/importing-and-displaying-large-data-sets-in-core-data/). Using a concurrent operations (achieved by overriding the `start` method) is not useful in this case. – Lorenzo B Jan 13 '13 at 14:04
1

This is not a concurrent operation. And your mergeChanges notification will probably never get called because as soon as the main method finishes the operation will be deallocated (which will probably be before the NSManagedObjectContextDidSaveNotification selector gets executed).

If you want a concurrent operation that sticks around until you are done with it, you need to override the start method and isConcurrent method. You are probably getting this error because your NSError or selector are going out of scope before completing. I suggest reading this article to understand how concurrent vs. non-concurrent operations work:

Please check Concurrent Operations Demystified by Dave Dribin for further details.

Lorenzo B
  • 33,216
  • 24
  • 116
  • 190
Joel
  • 15,654
  • 5
  • 37
  • 60
  • thanks for the answer, and i have to register to this notification [nc addObserver:self selector:@selector(mergeChanges:) name:NSManagedObjectContextDidSaveNotification object:self.managedObjectContext]; and insert this method, or i doens't need it anymore? – Piero Jan 13 '13 at 11:38
  • There is no need to create a concurrent operation here. I think @Piero uses operations added to a queue. – Lorenzo B Jan 13 '13 at 12:17
  • If you want a concurrent operation to work, then you need to make the changes I suggested so the operation doesn't terminate before the notification is received. You could also move the selector out of the operation and shunt it over to the main thread as @flexaddicted suggests. – Joel Jan 13 '13 at 16:54