2
votes

Hi my app is crashing when the managed object is changed in one child context(saved after) and deleted in other child context(saved first).
How to reproduce:
1.Create new project with 'Empty Application' template and core data enabled.
2.Change the managedObjectContext getter to following (i have changed the concurrency type)

- (NSManagedObjectContext *)managedObjectContext
{
    if (_managedObjectContext != nil) {
        return _managedObjectContext;
    }

    NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
    if (coordinator != nil) {
        _managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
        [_managedObjectContext setPersistentStoreCoordinator:coordinator];
    }
    return _managedObjectContext;
}

3.Please replace didfinishLaunching method to following

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    // Override point for customization after application launch.
    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];

    {
        //insert
        NSManagedObjectContext *insertingContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
        [insertingContext setParentContext:self.managedObjectContext];
        [insertingContext performBlockAndWait:^{
            Test *test = (Test *)[NSEntityDescription insertNewObjectForEntityForName:@"Test" inManagedObjectContext:insertingContext];
            test.test=@"test";
            [insertingContext save:nil];
            [self.managedObjectContext performBlockAndWait:^{
                [self.managedObjectContext save:nil];
            }];
            NSLog(@"inserted and saved to persistance store");
        }];
    }

    {
        //get the mo and change the property
        NSManagedObjectContext *acceesingContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
        {
            [acceesingContext setParentContext:self.managedObjectContext];
            [acceesingContext performBlockAndWait:^{
                NSFetchRequest *request = [[NSFetchRequest alloc] init] ;
                [request setEntity:[NSEntityDescription entityForName:@"Test" inManagedObjectContext:acceesingContext]];
                NSArray *results = [acceesingContext executeFetchRequest:request error:nil];
                if ([results count] > 0 )
                {
                    Test *test= [results objectAtIndex:0];
                    test.test=@"Hello";
                    NSLog(@"accessed and changed the property so that fault is fired");
                }

            }];
        }
        {
            NSManagedObjectContext *deletingContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
            [deletingContext setParentContext:self.managedObjectContext];
            [deletingContext performBlockAndWait:^{
                NSFetchRequest *request = [[NSFetchRequest alloc] init] ;
                [request setEntity:[NSEntityDescription entityForName:@"Test" inManagedObjectContext:deletingContext]];
                NSArray *results = [deletingContext executeFetchRequest:request error:nil];
                if ([results count] > 0 )
                {
                    Test *test= [results objectAtIndex:0];
                    [deletingContext deleteObject:test];
                    [deletingContext save:nil];
                    [self.managedObjectContext performBlockAndWait:^{
                        [self.managedObjectContext save:nil];
                    }];
                    NSLog(@"deleted and saved to persistance store");
                }
            }];
        }

        [acceesingContext performBlock:^{
            // it is crashing here, please help.
            [acceesingContext save:nil];
        }];
    }

    return YES;
}

4.Finally Add Entity named 'Test' with an attribute 'test' (NSString) and run the app

Problem what i am facing is, when one child moc has fetched a managed object and changed a property in it, while other child deletes and save the changes to persistent store. the crash occurs while saving the moc which has modified the mo.

Crash Report

*** Terminating app due to uncaught exception 'NSObjectInaccessibleException', reason: 'CoreData could not fulfill a fault for '0xd0000000001c0000 ''
*** First throw call stack:
(
    0   CoreFoundation                      0x0000000101bf0795 __exceptionPreprocess + 165
    1   libobjc.A.dylib                     0x0000000101953991 objc_exception_throw + 43
    2   CoreData                            0x0000000100278a93 _PFFaultHandlerLookupRow + 1075
    3   CoreData                            0x00000001003063a3 -[NSManagedObject(_NSInternalMethods) _updateFromRefreshSnapshot:includingTransients:] + 243
    4   CoreData                            0x00000001002aa563 -[NSManagedObjectContext(_NestedContextSupport) _copyChildObject:toParentObject:fromChildContext:] + 771
    5   CoreData                            0x00000001002aa01b -[NSManagedObjectContext(_NestedContextSupport) _parentProcessSaveRequest:inContext:error:] + 1019
    6   CoreData                            0x0000000100310243 __82-[NSManagedObjectContext(_NestedContextSupport) executeRequest:withContext:error:]_block_invoke + 563
    7   libdispatch.dylib                   0x0000000101fc205a _dispatch_barrier_sync_f_slow_invoke + 45
    8   libdispatch.dylib                   0x0000000101fd16fd _dispatch_client_callout + 8
    9   libdispatch.dylib                   0x0000000101fc146c _dispatch_main_queue_callback_4CF + 354
    10  CoreFoundation                      0x0000000101c4e729 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 9
    11  CoreFoundation                      0x0000000101b9b9a4 __CFRunLoopRun + 1764
    12  CoreFoundation                      0x0000000101b9aed3 CFRunLoopRunSpecific + 467
    13  GraphicsServices                    0x0000000103b893a4 GSEventRunModal + 161
    14  UIKit                               0x00000001005bba63 UIApplicationMain + 1010
    15  TestCrash                           0x00000001000040a3 main + 115
    16  libdyld.dylib                       0x000000010227e7e1 start + 0
    17  ???                                 0x0000000000000001 0x0 + 1
)
libc++abi.dylib: terminating with uncaught exception of type _NSCoreDataException
1
Also adding @ try @ catch @ finally block did not avoid the above crash.pradeepa
Tried all the different merge policies, still crashing.pradeepa

1 Answers

9
votes

The key here is not that line in the stack trace, it's the exception message:

*** Terminating app due to uncaught exception 'NSObjectInaccessibleException', reason: 'CoreData could not fulfill a fault for '0xd0000000001c0000 ''

If Core Data complains that an object is inaccessible, that means it could not find the object in the persistent store. Let's look at why this happens in your code:

  1. You create an instance using insertingContext and save changes there and in the parent. At this point, your one object is in the persistent store file.
  2. You fetch this instance using acceesingContext and make changes to it, but you do not save changes. At this point, acceesingContext has in-memory, unsaved changes to the object you just fetched.
  3. You fetch the object using deletingContext and delete it. Then you save changes in deletingContext and in the parent. At this point, you have removed the object from the persistent store file. However, and this is crucial, acceesingContext still has unsaved changes for that object.
  4. You save changes on acceesingContext. Since acceesingContext has unsaved changes on the object you fetched, it tries to update that object. But that object doesn't exist in the persistent store, because you deleted it. Since acceesingContext can't update a nonexistent object, it throws an exception and the app crashes.

One thing to keep in mind when using nested managed object contexts is that saving changes only pushes changes in one direction-- toward the parent. If you have two sibling contexts (in this case acceesingContext and deletingContext), saving changes on one child does not automatically update the other.

Since this is obviously demo code (thanks, BTW, makes it easier to follow!) it's hard to be completely certain what you would need to do in your real app code. A typical approach is to listen for NSManagedObjectContextDidSaveNotification and then use mergeChangesFromContextDidSaveNotification: to apply changes from one context into another one. That way when one context deletes an object, you can update other contexts to reflect that fact. This makes it possible to keep multiple managed object contexts in sync with each other.