Я столкнулся с такой же проблемой взаимоблокировки (что довольно часто встречается в SO), которая встречается в многоадресном сценарии NSManagedObjectContexts &. В некоторых контроллерах моего приложения мое приложение использует фоновые потоки для получения данных из веб-службы, и в этом же потоке он сохраняет его. В других случаях, когда имеет смысл не продвигаться дальше без сохранения (например, сохраняйте значения из формы, когда они нажимают «Далее»), сохранение выполняется в основном потоке. AFAIK не должно быть ничего плохого в теории, но иногда я могу сделать тупиковой случиться на призывСинтаксис CoreData с несколькими потоками
if (![moc save:&error])
... и это, кажется, всегда на фоне потока сохранить, когда происходит затор. Это не происходит при каждом звонке; на самом деле все наоборот: я должен использовать свое приложение на пару минут, а потом это произойдет.
Я прочитал все сообщения, которые я мог найти, а также документы Apple и т. Д., И я уверен, что следую рекомендациям. Конкретно, мое понимание работы с несколькими MOC/потоками сводится к:
- Каждая нить должна иметь свой собственный MOC.
- В этом потоке должен быть создан MOC потока, который не передается из одного потока в другой.
- NSManagedObject не может быть передан, но NSManagedObjectID может, и вы используете ID для раздувания NSManagedObject с использованием другого MOC.
- Изменения от одного MOC должны быть объединены с другим, если они оба используют тот же PersistentStoreCoordinator.
Некоторое время назад я наткнулся на некоторый код для MOC вспомогательного класса на this SO thread и обнаружил, что это было легко понять, и очень удобно в использовании, поэтому все мое MOC взаимодействие теперь через это. Вот мой ManagedObjectContextHelper класс во всей своей полноте:
#import "ManagedObjectContextHelper.h"
@implementation ManagedObjectContextHelper
+(void)initialize {
[[NSNotificationCenter defaultCenter] addObserver:[self class]
selector:@selector(threadExit:)
name:NSThreadWillExitNotification
object:nil];
}
+(void)threadExit:(NSNotification *)aNotification {
TDAppDelegate *delegate = (TDAppDelegate *)[[UIApplication sharedApplication] delegate];
NSString *threadKey = [NSString stringWithFormat:@"%p", [NSThread currentThread]];
NSMutableDictionary *managedObjectContexts = delegate.managedObjectContexts;
[managedObjectContexts removeObjectForKey:threadKey];
}
+(NSManagedObjectContext *)managedObjectContext {
TDAppDelegate *delegate = (TDAppDelegate *)[[UIApplication sharedApplication] delegate];
NSManagedObjectContext *moc = delegate.managedObjectContext;
NSThread *thread = [NSThread currentThread];
if ([thread isMainThread]) {
[moc setMergePolicy:NSErrorMergePolicy];
return moc;
}
// a key to cache the context for the given thread
NSString *threadKey = [NSString stringWithFormat:@"%p", thread];
// delegate.managedObjectContexts is a mutable dictionary in the app delegate
NSMutableDictionary *managedObjectContexts = delegate.managedObjectContexts;
if ([managedObjectContexts objectForKey:threadKey] == nil) {
// create a context for this thread
NSManagedObjectContext *threadContext = [[NSManagedObjectContext alloc] init];
[threadContext setPersistentStoreCoordinator:[moc persistentStoreCoordinator]];
[threadContext setMergePolicy:NSErrorMergePolicy];
// cache the context for this thread
NSLog(@"Adding a new thread:%@", threadKey);
[managedObjectContexts setObject:threadContext forKey:threadKey];
}
return [managedObjectContexts objectForKey:threadKey];
}
+(void)commit {
// get the moc for this thread
NSManagedObjectContext *moc = [self managedObjectContext];
NSThread *thread = [NSThread currentThread];
if ([thread isMainThread] == NO) {
// only observe notifications other than the main thread
[[NSNotificationCenter defaultCenter] addObserver:[self class] selector:@selector(contextDidSave:)
name:NSManagedObjectContextDidSaveNotification
object:moc];
}
NSError *error;
if (![moc save:&error]) {
NSLog(@"Failure is happening on %@ thread",[thread isMainThread][email protected]"main":@"other");
NSArray* detailedErrors = [[error userInfo] objectForKey:NSDetailedErrorsKey];
if(detailedErrors != nil && [detailedErrors count] > 0) {
for(NSError* detailedError in detailedErrors) {
NSLog(@" DetailedError: %@", [detailedError userInfo]);
}
}
NSLog(@" %@", [error userInfo]);
}
if ([thread isMainThread] == NO) {
[[NSNotificationCenter defaultCenter] removeObserver:[self class] name:NSManagedObjectContextDidSaveNotification
object:moc];
}
}
+(void)contextDidSave:(NSNotification*)saveNotification {
TDAppDelegate *delegate = (TDAppDelegate *)[[UIApplication sharedApplication] delegate];
NSManagedObjectContext *moc = delegate.managedObjectContext;
[moc performSelectorOnMainThread:@selector(mergeChangesFromContextDidSaveNotification:)
withObject:saveNotification
waitUntilDone:NO];
}
@end
Вот фрагмент многопоточной бит, где он, кажется тупиковой:
NSManagedObjectID *parentObjectID = [parent objectID];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^{
// GET BACKGROUND MOC
NSManagedObjectContext *backgroundContext = [ManagedObjectContextHelper managedObjectContext];
Parent *backgroundParent = (Parent*)[backgroundContext objectWithID:parentObjectID];
// HIT THE WEBSERVICE AND PUT THE RESULTS IN THE PARENT OBJECT AND ITS CHILDREN, THEN SAVE...
[ManagedObjectContextHelper commit];
dispatch_sync(dispatch_get_main_queue(), ^{
NSManagedObjectContext *mainManagedObjectContext = [ManagedObjectContextHelper managedObjectContext];
parent = (Parent*)[mainManagedObjectContext objectWithID:parentObjectID];
});
});
conflictList в ошибке, кажется, предполагает, что это что-то делать с ObjectID родительского объекта:
conflictList = (
"NSMergeConflict (0x856b130) for NSManagedObject (0x93a60e0) with objectID '0xb07a6c0 <x-coredata://B7371EA1-2532-4D2B-8F3A-E09B56CC04F3/Child/p4>'
with oldVersion = 21 and newVersion = 22
and old object snapshot = {\n parent = \"0xb192280 <x-coredata://B7371EA1-2532-4D2B-8F3A-E09B56CC04F3/Parent/p3>\";\n name = \"New Child\";\n returnedChildId = 337046373;\n time = 38;\n}
and new cached row = {\n parent = \"0x856b000 <x-coredata://B7371EA1-2532-4D2B-8F3A-E09B56CC04F3/Parent/p3>\";\n name = \"New Child\";\n returnedChildId = 337046373;\n time = 38;\n}"
);
Я попытался положить в refreshObject звонки, как только я получил достать MOC, с теорией будучи го если это MOC, который мы использовали раньше (например, мы использовали MOC в основном потоке раньше, и вполне вероятно, что это то же самое, что и вспомогательный класс), тогда, возможно, сохранение в другом потоке означает, что нам нужно явно обновить. Но это не имело никакого значения, это все еще тупики, если я продолжаю нажимать достаточно долго.
У кого-нибудь есть идеи?
Редактирование: Если у меня установлена точка останова для всех исключений, отладчик автоматически приостанавливается на линии if (![moc save:&error])
, поэтому кнопка воспроизведения/паузы уже приостановлена и отображает треугольник воспроизведения. Если я отключу точку останова для всех исключений, тогда она фактически регистрирует конфликт и продолжается - вероятно, потому, что в настоящее время политика слияния установлена на NSErrorMergePolicy, поэтому я не думаю, что это фактически блокировка потоков. Here's a screehshot состояния обоих потоков во время паузы.
Когда ваше приложение блокируется, в каком методе он висит на основной теме? – lassej
@lassej Вот распечатка Thread1: libsystem_kernel.dylib'mach_msg_trap: 0x98030c18: movl $ 4294967265,% eax 0x98030c1d: calll 0x9803449a; _sysenter_trap 0x98030c22: ret 0x98030c23: nop – bobsmells
Я не уверен, что это то, что вам нужно. Thread6 включен в [NSManagedObjectContext save]. Я не уверен, что Thread1 фактически заблокирован, но это тупик на MOC двух потоков, если я правильно понял. – bobsmells