I have two NSManagedObjectContext named importContext and childContext . childContext is a child of importContext , and both are NSPrivateQueueConcurrencyType .
To remove things from the main thread, I do a ton of work in the importContext queue. This job involves a lot of fetching and saving, so itβs convenient to wrap it all inside performBlockAndWait: importContext (this is necessary for a synchronous operation, because the code I received after performBlockAndWait depends on its results).
At some point during this work, I may need to create new managed objects from the JSON results. These JSON values ββmay not be valid and may not perform my checks, so after I create the objects, I need to be able to break them if they are not good. Here comes the childContext . I insert my new object into it, and if its JSON attributes do not make sense, I interrupt childContext .
The problem occurs when I need to save a childContext . I expect him to have his own private queue separate from the parent queue. However, this causes a deadlock ONLY on iOS 7 (not iOS 8). When I run the same code on simulators and iOS 8 devices, childContext creates its own queue in a separate thread and saves correctly.
It seems that when I launch iOS 7, childContext tries to make save: in the parent queue, but the parent is waiting for its child, which causes a dead end. In iOS 8, this does not happen. Does anyone know why?
Here is the simplified code:
-(NSManagedObjectContext *)importContext { NSManagedObjectContext* moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; moc.persistentStoreCoordinator = [self storeCoordinator]; return moc; } -(void)updateItems:(NSArray*)ItemDescriptions { [self.importContext performBlockAndWait:^{ //get info and update ... ... if(needToCreateNewItem){ NSManagedObjectContext* childContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; childContext.parentContext = self.importedContext; //Insert and create new item ... [childContext performBlockAndWait:^{ id newObject = [NSEntityDescription insertNewObjectForEntityForName:[self entityName] inManagedObjectContext:childContext]; }]; ... // Do something with this object if([newObject isReadyToSave]) __block NSError* e = nil; __block BOOL saveSucceeded = NO; [childContext performBlockAndWait:^{ saveSucceeded = [childContext save:&e]; // DEADLOCK ON iOS 7!!!! }]; } .... } }]; }
A simple job is working on a separate dispatch queue (instead of the importContext queue), but the reason I ask this question is because I want to understand the reason why this happens. I think that the preservation of the child should occur only in its own queue.
UPDATE 1
Re. Marcus questions:
updateItems: called from NSInvocationOperation in the operation queue, so it disconnects from the main queue.
In iOS 7, I can pause the application at any time and view the stack, and the context queue of the managed object will be blocked:
(lldb) bt * thread #7: tid = 0xed07, 0x38546aa8 libsystem_kernel.dylib`semaphore_wait_trap + 8, queue = 'NSManagedObjectContext Queue' frame #0: 0x38546aa8 libsystem_kernel.dylib`semaphore_wait_trap + 8 frame #1: 0x385bbbac libsystem_platform.dylib`_os_semaphore_wait + 12 frame #2: 0x3848461a libdispatch.dylib`_dispatch_barrier_sync_f_slow + 138 frame #3: 0x2d4f3df2 CoreData`_perform + 102 frame #4: 0x2d4fe1ac CoreData`-[NSManagedObjectContext(_NestedContextSupport) executeRequest:withContext:error:] + 240 frame #5: 0x2d492f42 CoreData`-[NSManagedObjectContext save:] + 826 * frame #6: 0x000c1c96 DBDevApp`__69+[DBManagedObject createWithAttributes:inManagedObjectContext:error:]_block_invoke77(.block_descriptor=<unavailable>) + 118 at DBManagedObject.m:117 frame #7: 0x2d4f6934 CoreData`developerSubmittedBlockToNSManagedObjectContextPerform + 88 frame #8: 0x3847e81e libdispatch.dylib`_dispatch_client_callout + 22 frame #9: 0x384847ca libdispatch.dylib`_dispatch_barrier_sync_f_invoke + 26 frame #10: 0x2d4f6a72 CoreData`-[NSManagedObjectContext performBlockAndWait:] + 106 frame #11: 0x000c1916 DBDevApp`+[DBManagedObject createWithAttributes:inManagedObjectContext:error:](self=0x005c1790, _cmd=0x0054a033, attributes=0x188e context=0x17500800, error=0x02e68ae8) + 658 at DBManagedObject.m:116 frame #12: 0x000fe138 DBDevApp`-[DBAPIController createOrUpdateItems:withIDs:IDKeys:ofClass:amongExistingItems:withFindByIDPredicate:](self=0x17775de0, _cmd=0x0054de newItemDescriptions=0x188eada0, itemIDs=0x18849580, idKey=0x0058e290, class=0x005c1790, existingItems=0x1756b560, findByID=0x18849c80) + 2472 at DBAPIController.m:972 frame #13: 0x00100ca0 DBDevApp`__39-[DBAPIController updatePatientGroups:]_block_invoke(.block_descriptor=0x02e68ce0) + 476 at DBAPIController.m:1198 frame #14: 0x2d4f6934 CoreData`developerSubmittedBlockToNSManagedObjectContextPerform frame #15: 0x3847e81e libdispatch.dylib`_dispatch_client_callout + 22 frame #16: 0x384847ca libdispatch.dylib`_dispatch_barrier_sync_f_invoke + 26 frame #17: 0x2d4f6a72 CoreData`-[NSManagedObjectContext performBlockAndWait:] + 106 frame #18: 0x00100a96 DBDevApp`-[DBAPIController updatePatientGroups:](self=0x17775de0, _cmd=0x0054dfcd, groupsArray=0x188eada0) + 214 at DBAPIController.m:1191 frame #19: 0x2d721584 CoreFoundation`__invoking___ + 68 frame #20: 0x2d66c0da CoreFoundation`-[NSInvocation invoke] + 282 frame #21: 0x2e0f3d2c Foundation`-[NSInvocationOperation main] + 112 frame #22: 0x2e0515aa Foundation`-[__NSOperationInternal _start:] + 770 frame #23: 0x2e0f576c Foundation`__NSOQSchedule_f + 60 frame #24: 0x38484f10 libdispatch.dylib`_dispatch_queue_drain$VARIANT$mp + 488 frame #25: 0x38484c96 libdispatch.dylib`_dispatch_queue_invoke$VARIANT$mp + 42 frame #26: 0x38485a44 libdispatch.dylib`_dispatch_root_queue_drain + 76 frame #27: 0x38485d28 libdispatch.dylib`_dispatch_worker_thread2 + 56 frame #28: 0x385c0bd2 libsystem_pthread.dylib`_pthread_wqthread + 298 : IDKeys: ofClass: amongExistingItems: withFindByIDPredicate:] (self = 0x17775de0, _cmd = 0x0054de newItemDescriptions = 0x188eada0, itemIDs = 0x18849580, idKey = 0x0058e290, class = 0x005c1790, existingItems = 0x1756b560 (lldb) bt * thread #7: tid = 0xed07, 0x38546aa8 libsystem_kernel.dylib`semaphore_wait_trap + 8, queue = 'NSManagedObjectContext Queue' frame #0: 0x38546aa8 libsystem_kernel.dylib`semaphore_wait_trap + 8 frame #1: 0x385bbbac libsystem_platform.dylib`_os_semaphore_wait + 12 frame #2: 0x3848461a libdispatch.dylib`_dispatch_barrier_sync_f_slow + 138 frame #3: 0x2d4f3df2 CoreData`_perform + 102 frame #4: 0x2d4fe1ac CoreData`-[NSManagedObjectContext(_NestedContextSupport) executeRequest:withContext:error:] + 240 frame #5: 0x2d492f42 CoreData`-[NSManagedObjectContext save:] + 826 * frame #6: 0x000c1c96 DBDevApp`__69+[DBManagedObject createWithAttributes:inManagedObjectContext:error:]_block_invoke77(.block_descriptor=<unavailable>) + 118 at DBManagedObject.m:117 frame #7: 0x2d4f6934 CoreData`developerSubmittedBlockToNSManagedObjectContextPerform + 88 frame #8: 0x3847e81e libdispatch.dylib`_dispatch_client_callout + 22 frame #9: 0x384847ca libdispatch.dylib`_dispatch_barrier_sync_f_invoke + 26 frame #10: 0x2d4f6a72 CoreData`-[NSManagedObjectContext performBlockAndWait:] + 106 frame #11: 0x000c1916 DBDevApp`+[DBManagedObject createWithAttributes:inManagedObjectContext:error:](self=0x005c1790, _cmd=0x0054a033, attributes=0x188e context=0x17500800, error=0x02e68ae8) + 658 at DBManagedObject.m:116 frame #12: 0x000fe138 DBDevApp`-[DBAPIController createOrUpdateItems:withIDs:IDKeys:ofClass:amongExistingItems:withFindByIDPredicate:](self=0x17775de0, _cmd=0x0054de newItemDescriptions=0x188eada0, itemIDs=0x18849580, idKey=0x0058e290, class=0x005c1790, existingItems=0x1756b560, findByID=0x18849c80) + 2472 at DBAPIController.m:972 frame #13: 0x00100ca0 DBDevApp`__39-[DBAPIController updatePatientGroups:]_block_invoke(.block_descriptor=0x02e68ce0) + 476 at DBAPIController.m:1198 frame #14: 0x2d4f6934 CoreData`developerSubmittedBlockToNSManagedObjectContextPerform frame #15: 0x3847e81e libdispatch.dylib`_dispatch_client_callout + 22 frame #16: 0x384847ca libdispatch.dylib`_dispatch_barrier_sync_f_invoke + 26 frame #17: 0x2d4f6a72 CoreData`-[NSManagedObjectContext performBlockAndWait:] + 106 frame #18: 0x00100a96 DBDevApp`-[DBAPIController updatePatientGroups:](self=0x17775de0, _cmd=0x0054dfcd, groupsArray=0x188eada0) + 214 at DBAPIController.m:1191 frame #19: 0x2d721584 CoreFoundation`__invoking___ + 68 frame #20: 0x2d66c0da CoreFoundation`-[NSInvocation invoke] + 282 frame #21: 0x2e0f3d2c Foundation`-[NSInvocationOperation main] + 112 frame #22: 0x2e0515aa Foundation`-[__NSOperationInternal _start:] + 770 frame #23: 0x2e0f576c Foundation`__NSOQSchedule_f + 60 frame #24: 0x38484f10 libdispatch.dylib`_dispatch_queue_drain$VARIANT$mp + 488 frame #25: 0x38484c96 libdispatch.dylib`_dispatch_queue_invoke$VARIANT$mp + 42 frame #26: 0x38485a44 libdispatch.dylib`_dispatch_root_queue_drain + 76 frame #27: 0x38485d28 libdispatch.dylib`_dispatch_worker_thread2 + 56 frame #28: 0x385c0bd2 libsystem_pthread.dylib`_pthread_wqthread + 298
The code shown above was a simplified version. The part in which I create a new child context is inside a class called DBManagedObject . Here is a screenshot of the entire package:

Update 2 - DBManagedObject Explanation
DBManagedObject is the base class for all of my core data classes. It mainly handles conversion to and from JSON-parsed dictionaries. It has 3 main methods: +createWithAttributes:inManagedObjectContext:error: -updateWithAttributes:error: and attributes .
+createWithAttributes:inManagedObjectContext:error: creates a child context of the provided context of the managed object, inserts a new object into the child context, and calls updateWithAttributes:error: on this object. If the update is successful (i.e., all the values ββthat we want to set on this object make sense), it saves the child context, receives a link to the new object in the MOC, which is included as a parameter, and returns this link:
NSManagedObjectContext* childContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; childContext.parentContext = context; __block id newObject; [childContext performBlockAndWait:^{ newObject = [NSEntityDescription insertNewObjectForEntityForName:[self entityName] inManagedObjectContext:childContext]; }]; if ([newObject updateWithAttributes:attributes error:error]) { NSError* e = nil; if ([childContext save:&e]) { id parentContextObject = [context objectWithID:[(NSManagedObject*)newObject objectID]]; return parentContextObject; } else { if (error != NULL) { *error = e; } return nil; } } else return nil;
updateWithAttributes:error: does the heavy lifting of translation keys between JSON keys into the ones I used in my data model as properties of objects. (i.e., "first_name" becomes "firstName"). It also formats JSON values ββif necessary (date strings become NSDate s). He also establishes a relationship.