我有两个NSManagedObjectContext
名为importContext
和childContext
。 childContext
是importContext
的孩子,他们都是NSPrivateQueueConcurrencyType
。
为了避开主线程,我在importContext
的队列中做了很多工作。这项工作涉及大量的提取和保存,因此将整个内容包装在performBlockAndWait:
的{{1}}内是很方便的( 需要同步操作,因为importContext
之后的代码取决于其结果)。
在此工作期间的某个时刻,我可能需要从JSON结果创建新的托管对象。这些JSON值可能无效并且无法通过验证,因此在创建对象后,如果它们不合适,我需要能够抛弃它们。这就是performBlockAndWait
的用武之地。我将新对象插入其中,如果它的JSON属性最终没有意义,我放弃了childContext
。
当我需要保存childContext
时,问题就出现了。我希望它有自己的私有队列,与父队列分开。但是,这会导致iOS 7(不是iOS 8)上的死锁。当我在iOS 8模拟器和设备上运行相同的代码时,childContext
会在单独的线程上创建自己的队列并正确保存。
似乎在我运行iOS 7时,childContext
正在尝试在父队列中执行childContext
,但父级正在等待其子级导致死锁。在iOS 8中,这不会发生。有谁知道为什么?
以下是简化代码:
save:
简单的解决方法是将工作保持在单独的调度队列(而不是 -(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!!!!
}];
}
....
}
}];
}
的队列中),但我问这个问题的原因是因为我想要了解发生这种情况的根本原因。我认为孩子的保存应该只发生在自己的队列中。
更新1
重新。马库斯'问题:
importContext
从操作队列中的updateItems:
调用,因此它不在主队列中。
在iOS 7上,我可以随时暂停应用程序并查看堆栈,托管对象上下文的队列将陷入僵局:
NSInvocationOperation
我上面展示的代码是简化版。我创建新子上下文的部分位于名为(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
的类中。这是整个堆栈的屏幕截图:
更新2 - 解释DBManagedObject
DBManagedObject
是我所有核心数据类的基类。它基本上处理与JSON解析的字典之间的转换。它有3种主要方法:DBManagedObject
,+createWithAttributes:inManagedObjectContext:error:
和-updateWithAttributes:error:
。
attributes
:创建所提供的托管对象上下文的子上下文,在子上下文中插入一个新对象,并在该对象上调用+createWithAttributes:inManagedObjectContext:error:
。如果更新成功(即,我们想要在此对象上设置的所有值都有意义),它将保存子上下文,获取对作为参数作为参数的MOC中的新对象的引用,并返回该引用:< / p>
updateWithAttributes:error:
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;
:将JSON密钥之间的密钥转换为我在数据模型中使用的密钥,作为实体的属性。 (即&#39; first_name&#39;成为&#39; firstName&#39;)。如果需要,它还会格式化JSON值(日期字符串变为updateWithAttributes:error:
s)。它还建立了关系。
答案 0 :(得分:0)
谁在致电-updateItems:
?如果它在主队列中进入,那么你就有问题,因为你阻止了它。
假设情况并非如此,您是否可以从Xcode共享显示截止日期的线程堆栈?特别是扩展了队列并扩展了主队列?
一旦我好好看看那个堆栈,我会更新我的答案。
Quileish是正确的,你没有正确插入孩子。该儿童MOC的任何活动都应在-performBlock:
或-performBlockAndWait:
内。我会扩展-performBlockAndWait:
以涵盖对象的整个创建和决策,而不仅仅是保存。
-createWithAttributes:inManagedObjectContext:error:
做什么?似乎该方法正在做一些不合适的事情。也许试图强制永久ID或什么?
如有疑问,您的-createWithAttributes:inManagedObjectContext:error:
就是您的问题。当你致电-objectWithID:
时,你会在NSPersistentStoreCoordinator
处一直向下开火,这会导致锁定。
此外,这种方法没有任何帮助。创建一个上下文只是为了创建一个对象,然后立即在另一个上下文中抓取该对象,这绝对没有价值。一切伤害,没有好处。完全删除它,只需在您实际要使用的上下文中创建对象。从正在使用的上下文中保存或丢弃它。
不要聪明。
答案 1 :(得分:0)
从查看你的代码我发现你有2 [childContext performBlockAndWait:^ {是嵌套的。删除其中一个应清除您在ios7中的问题。代码已经在该线程中运行,您不必再次执行此操作。
始终检查是否有相同上下文的嵌套performBlocks。这导致我的应用程序在ios7之前陷入僵局并在ios8中工作
检查的方法是当你看到死锁时,在调试器中按暂停,看看所有线程正在运行的块。查看该特定代码并检查嵌套块。