Z S
Z S

Reputation: 7493

Crash with NSManagedObjectContext refreshObject

I am getting an occasional crash report from users (which I'm unable to reproduce myself); it's something to do with NSManagedObjectContext refreshObject. The crash message is

"An NSManagedObjectContext cannot refresh objects in other contexts"

The code where it crashes is here:

dispatch_async(self.filterMainQueue, ^{
    NSArray *items = [Person getAllNonPrivatePersonsWithContext: self.backgroundContextImage];

    if (items.count) {

        for (Person *person in items) {

                [self.backgroundContextImage performBlockAndWait: ^{
                    [person loadContactReferenceAndImage];

                    // crashes here
                    [self.backgroundContextImage refreshObject: person mergeChanges:NO];
                }];
        }               
    }
});

+ (NSArray *) getAllNonPrivatePersonsWithContext: (NSManagedObjectContext *) context{

    __block NSArray *items = nil;

    [context performBlockAndWait: ^{
        NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
        // Edit the entity name as appropriate.
        NSEntityDescription *entity = [NSEntityDescription entityForName:@"Person" inManagedObjectContext: context];
        [fetchRequest setEntity:entity];
        // Set the batch size to a suitable number.
        //[fetchRequest setFetchBatchSize: 500];
        [fetchRequest setPredicate: [NSPredicate predicateWithFormat: @"isContactPrivate == FALSE"]];
        [fetchRequest setReturnsObjectsAsFaults: FALSE];
        [fetchRequest setIncludesPendingChanges: FALSE];

        NSError *error = nil;
        items = [context executeFetchRequest:fetchRequest error:&error];

    }];
    return items;
}

I'm not quite sure why this is crashing. The backgroundContextImage is created using NSPrivateQueueConcurrencyType, and I make sure I'm using performBlockAndWait when fetching and accessing the managed objects with that context. The filterMainQueue is a serial queue, to help with doing this work in a background thread instead of the main thread.

The crash report looks like this:

Exception Type:  SIGABRT
Exception Codes: #0 at 0x197fb7140
Crashed Thread:  3

Application Specific Information:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'An NSManagedObjectContext cannot refresh objects in other contexts.'

Last Exception Backtrace:
0   CoreFoundation                       0x0000000182a98f48 __exceptionPreprocess + 124
1   libobjc.A.dylib                      0x000000019764bf80 objc_exception_throw + 52
2   CoreData                             0x0000000182786898 -[NSManagedObjectContext refreshObject:mergeChanges:] + 1320
3   CJournal                             0x00000001001729c4 __75-[ContactsSyncController loadContactImagesAndLinksWithPermissionWithBlock:]_block_invoke_2 (ContactsSyncController.m:102)
4   CoreData                             0x00000001827dc900 developerSubmittedBlockToNSManagedObjectContextPerform + 192
5   libdispatch.dylib                    0x0000000197e696a8 _dispatch_client_callout + 12
6   libdispatch.dylib                    0x0000000197e74954 _dispatch_barrier_sync_f_invoke + 96
7   CoreData                             0x00000001827dc7e8 -[NSManagedObjectContext performBlockAndWait:] + 248
8   CJournal                             0x00000001001728bc __75-[ContactsSyncController loadContactImagesAndLinksWithPermissionWithBlock:]_block_invoke (ContactsSyncController.m:97)
9   libdispatch.dylib                    0x0000000197e696e8 _dispatch_call_block_and_release + 20
10  libdispatch.dylib                    0x0000000197e696a8 _dispatch_client_callout + 12
11  libdispatch.dylib                    0x0000000197e756ec _dispatch_queue_drain + 860
12  libdispatch.dylib                    0x0000000197e6d1ac _dispatch_queue_invoke + 460
13  libdispatch.dylib                    0x0000000197e696a8 _dispatch_client_callout + 12
14  libdispatch.dylib                    0x0000000197e77b40 _dispatch_root_queue_drain + 2136
15  libdispatch.dylib                    0x0000000197e772dc _dispatch_worker_thread3 + 108
16  libsystem_pthread.dylib              0x000000019807d470 _pthread_wqthread + 1088
17  libsystem_pthread.dylib              0x000000019807d020 start_wqthread + 0

Any ideas?

Upvotes: 1

Views: 1606

Answers (1)

Z S
Z S

Reputation: 7493

The problem was that, under special circumstances, something else in the app would replace the persistentStore and reset the managedObjectContext, and even though the loadContactReferenceAndImage and refreshObject calls are both within a performBlockAndWait. The solution I came up with was to confirm if the managedObjectContext was the same, and only then refresh the object:

       [self.backgroundContextImage performBlockAndWait: ^{
              [person loadContactReferenceAndImage];

              // MOC could have been updated by another thread
              if (person.managedObjectContext == self.backgroundContextImage) {
                  [self.backgroundContextImage refreshObject: person mergeChanges:NO];
              }

       }];

Upvotes: 1

Related Questions