2014-04-22 4 views
0

У меня возникла проблема с реализацией отношений между родителями и дочерними элементами между двумя объектами NSManagedObjectContext. Мое приложение импортирует большое количество данных из веб-службы, которая вызывает отставание пользовательского интерфейса при сохранении контекста. Так что в моем AppDelegate я создал родительский контекст (мастер) с NSPrivateQueueConcurrencyType:Проблема с несколькими NSManagedObjectContexts

- (NSManagedObjectContext *)masterMOC 
{ 
    if (_masterMOC != nil) { 
     return _masterMOC; 
    } 

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

... и контекст ребенка (основной) с NSMainQueueConcurrencyType. Я установил parentContext главного MOC, чтобы masterMOC:

- (NSManagedObjectContext *)mainMOC 
{ 
    if (_mainMOC != nil) { 
    return _mainMOC; 
    } 

    _mainMOC = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType]; 
    [_mainMOC setUndoManager:nil]; 
    [_mainMOC setParentContext:[self masterMOC]]; 

    return _mainMOC; 
} 

В моей applicationDidFinishLaunching я пнуть операцию импорта, которая запрашивает веб-службу и сохраняет результаты на мастер (PrivateQueue) контекст. Я также регистрируюсь для NSManagedObjectContextDidSaveNotification и пытаюсь объединить эти изменения с дочерним mainMOC.

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 
{ 
    NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; 
    [notificationCenter addObserver:self selector:@selector(contextChanged:) name:NSManagedObjectContextDidSaveNotification object:self.masterMOC]; 

    RequestHandler *handler = [[RequestHandler alloc] initWithManagedObjectContext:self.masterMOC]; 
    [handler importAllViews]; 

    ... 

    return YES; 
} 

- (void) contextChanged: (NSNotification *) notification 
{ 
    // Only interested in merging from master into main. 
    if ([notification object] != self.masterMOC) return; 

    [self.mainMOC performBlock:^{ 
    [self.mainMOC mergeChangesFromContextDidSaveNotification:notification]; 
    }]; 
} 

Класс RequestHandler имеет метод saveContext, который сохраняет главный контекст на правильной резьбы:

@implementation KDBRequestHandler 

... 

- (void) saveContext 
{  
    [self.managedObjectContext performBlock:^{ 
    NSError *error; 
    if (![self.managedObjectContext save:&error]) { 
     [NSException raise:@"Unable to save build details." format:@"Error saving context: %@", error]; 
    } 
    }]; 
} 

... 

@end 

Я проверить импорт, чтобы быть правильно экономить свои объекты на фоне потока с инструментами. Моя проблема связана с контекстом управляемого дочернего объекта, который имеет NSMainQueueConcurrencyType. После того, как импорт стартовал, приложение didFinishLaunching инициализирует пользовательский интерфейс как стандартный. Контроллерам представлений назначается mainMOC.

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 
{ 
    // Override point for customization after application launch. 
    if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) { 
    UISplitViewController *splitViewController = (UISplitViewController *)self.window.rootViewController; 
    UINavigationController *navigationController = [splitViewController.viewControllers lastObject]; 
    splitViewController.delegate = (id)navigationController.topViewController; 

    UINavigationController *masterNavigationController = splitViewController.viewControllers[0]; 
    KDBMasterViewController *controller = (KDBMasterViewController *)masterNavigationController.topViewController; 
    controller.managedObjectContext = self.mainMOC; 
    } else { 
    UINavigationController *navigationController = (UINavigationController *)self.window.rootViewController; 
    KDBMasterViewController *controller = (KDBMasterViewController *)navigationController.topViewController; 
    controller.managedObjectContext = self.mainMOC; 
    } 

    return YES; 
} 

MasterViewController - это, по сути, контроллер шаблонов, созданный для вас при создании основного проекта данных. Это fetchedResultsController заканчивает выполнение своей работы на mainMOC, поэтому основной поток.

- (NSFetchedResultsController *)fetchedResultsController 
{ 
    if (_fetchedResultsController != nil) { 
    return _fetchedResultsController; 
    } 

    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; 
    // Edit the entity name as appropriate. 
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Job" inManagedObjectContext:self.managedObjectContext]; 
    [fetchRequest setEntity:entity]; 

    // Set the batch size to a suitable number. 
    [fetchRequest setFetchBatchSize:20]; 

    // Edit the sort key as appropriate. 
    NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"name" ascending:NO]; 
    NSArray *sortDescriptors = @[sortDescriptor]; 

    [fetchRequest setSortDescriptors:sortDescriptors]; 

    // Edit the section name key path and cache name if appropriate. 
    // nil for section name key path means "no sections". 
    NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:@"Master"]; 
    aFetchedResultsController.delegate = self; 
    self.fetchedResultsController = aFetchedResultsController; 

    NSError *error = nil; 
    if (![self.fetchedResultsController performFetch:&error]) { 
    // Replace this implementation with code to handle the error appropriately. 
    // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. 
    NSLog(@"Unresolved error %@, %@", error, [error userInfo]); 
    abort(); 
    } 

    return _fetchedResultsController; 
} 

При начальной загрузке, когда база данных свежая и пустая, все работает должным образом. Данные загружаются в фоновом режиме и заполняются в UITableView MasterViewController, как ожидалось. Однако при последующих запусках приложение часто выходит из строя в FetchedResultsController MasterViewController. Выборки терпит неудачу с этой ошибкой:

*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '+entityForName: could not locate an NSManagedObjectModel for entity name 'Job'' 
*** First throw call stack: 
(
0 CoreFoundation      0x01c6a1e4 __exceptionPreprocess + 180 
1 libobjc.A.dylib      0x019678e5 objc_exception_throw + 44 
2 CoreData       0x002c6a1b +[NSEntityDescription entityForName:inManagedObjectContext:] + 251 
3 JMobile      0x00032ad4 -[KDBMasterViewController fetchedResultsController] + 340 
4 JMobile      0x00031d6e -[KDBMasterViewController numberOfSectionsInTableView:] + 78 
5 UIKit        0x0088e712 -[UITableViewRowData(UITableViewRowDataPrivate) _updateNumSections] + 102 
6 UIKit        0x0088f513 -[UITableViewRowData invalidateAllSections] + 69 
7 UIKit        0x006fa6ea -[UITableView _updateRowData] + 197 
.... 

Поскольку авария не бывает 100% от времени я подозревал проблему параллелизма. Я подтвердил, отметив, что родительский контекст mainMOC был равен нулю в момент запроса запроса fetchedResultsController. Как я могу обеспечить, чтобы дочерний контекст (mainMOC) был правильно установлен с родителем, прежде чем пытаться выполнить запросы?

+0

попробуйте добавить строку: '[self mainMOC];' после добавления наблюдателя к сохранению вашего 'masterMOC' и перед началом импорта. Вы можете прочитать [ЭТО] (http://stackoverflow.com/questions/22994183/core-data-changes-dont-merge/23019665#23019665) для краткого обсуждения условий гонки в аналогичном случае (касается того, какой поток контексты параллельно). –

+0

@ DanShelly - Да, это сработало! Спасибо за простое исправление для решения проблемы гонки. Есть ли что-то архитектурное, что мне не хватает этой картины? Я все еще не вижу данных в MasterViewController (mainMOC) при последующих запусках. Никаких сбоев, но данных нет. Опять же, он работает на первоначальный импорт. Я думаю, что mainMOC должен получить от своего родителя (masterMOC), который должен, в свою очередь, извлекаться из PSC. Я могу начать новый вопрос, если это отдельная проблема, но я думаю, что мне не хватает важной части этой парадигмы. – kbeal

+0

В ваших инициализаторах MOC (ленивая инициализация) помните, что 'if' не является блокировкой. если у вас есть несколько потоков, обращающихся к этому методу, вы должны либо убедиться, что контексты предварительно инициализированы, либо доступ к их процессу инициализации синхронизирован с блокировкой. Что касается недостающих данных ... я не вижу ничего плохого в вашем коде извлечения. убедитесь, что вы не удаляете хранилище в каждом прогоне (или открываете новый). –

ответ

1

Существует состояние гонки доступа к вашим mainMOC:
Добавление строки: [self mainMOC]; после добавления наблюдателя к экономии вашего masterMOC и прежде чем начать свой импорт, будет работать вокруг MOC гонки инициализации.

Вы можете прочитать THIS для обсуждения условий гонки в аналогичном случае (с учетом того, какой поток обращается к вашим контекстам параллельно).

Код шаблона, предлагаемый Apple, хорош там, где у вас есть только один поток, инициализирующий ваши контексты (те, которые не являются временными), в противном случае вы должны синхронизировать инициализацию с помощью своего рода механизма блокировки потоков.

Смежные вопросы