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
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:
insertingContext
and save changes there and in the parent. At this point, your one object is in the persistent store file.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.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.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.