Как создать несколько объектов в фоновом режиме?

Я использую MagicalRecord 2.0.3, и я не могу понять, как сохранить данные в фоновом режиме.

Согласно документации, что-то вроде этого должно работать:

[MagicalRecord saveInBackgroundWithBlock:^(NSManagedObjectContext *localContext) {
    // Do this hundreds of times
    [MyObject createInContext:localContext];
}];

Однако ничего не сохраняется в базе данных. Я видел, как несколько человек отправляли решения, подобные этому:

[MagicalRecord saveInBackgroundWithBlock:^(NSManagedObjectContext *localContext) {
    // Do this hundreds of times
    [MyObject createInContext:localContext];
} completion:^{
    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
        [[NSManagedObjectContext defaultContext] saveNestedContexts];
    }];
}];

Это сохраняет мои данные в базе данных, однако, поскольку сохранение происходит в основном потоке, мое приложение не отвечает на какое-то время (с моим набором данных, около 3 секунд, который слишком длинный).

Я также пробовал это, но он также блокируется при сохранении:

self.queue = [[NSOperationQueue alloc] init];

[self.queue addOperationWithBlock:^{
    NSManagedObjectContext *localContext = [NSManagedObjectContext contextForCurrentThread];

    // Do this hundreds of times
    [MyObject createInContext:localContext];

    [localContext saveNestedContexts];
}];

И, наконец, тот же эффект блокировки с помощью этого кода:

dispatch_queue_t syncQueue = dispatch_queue_create("Sync queue", NULL);
dispatch_async(syncQueue, ^{
    NSManagedObjectContext *localContext = [NSManagedObjectContext contextForCurrentThread];

    // Do this hundreds of times
    [MyObject createInContext:localContext];

    [[NSManagedObjectContext contextForCurrentThread] saveNestedContexts];
});

Итак, каков наилучший способ решить эту проблему? Мне нужно создать сотни объектов в фоновом режиме, и приложение должно оставаться отзывчивым.

Ответ 1

MagicalRecord использует дочерний контекст при выполнении работы в фоновом режиме. Это отлично подходит для небольших изменений, но при импорте больших объемов данных будет создано чрезмерное блокирование основных потоков.

Способ сделать это - использовать параллельный NSManagedObjectContext и объединить себя с уведомлением NSManagedObjectContextDidSaveNotification и методом mergeChangesFromContextDidSaveNotification. См. Тесты производительности здесь: http://floriankugler.com/blog/2013/5/11/backstage-with-nested-managed-object-contexts

При сохранении вложенных контекстов все необходимо скопировать в родительский контекст. В противоположность этому объекты, которые не были выбраны (в контексте, в которые вы сливаетесь), не будут объединены с помощью mergeChangesFromContextDidSaveNotification. Это то, что делает его быстрее.

У вас могут возникнуть проблемы, если вы хотите сразу же отобразить эти результаты после сохранения в партиях и с помощью NSFetchResultsController. См. Следующий вопрос для решения: NSFetchedResultsController с предикатом игнорирует изменения, объединенные из другого NSManagedObjectContext

Для получения дополнительных рекомендаций по производительности рассмотрите этот вопрос: Внедрение быстрого и эффективного импорта основных данных на iOS 5

Создайте свой собственный контекст.

NSManagedObjectContext *importContext = [[NSManagedObjectContext alloc] 
                          initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[importContext setPersistentStoreCoordinator:yourPersistentStoreCoordinator];
[importContext setUndoManager:nil]; // For importing you don't need undo: Faster

// do your importing with the new importContext
// …

NSError* error = nil;
if(importContext.hasChanges) {
  if(![importContext save:&error]) {
      NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
  } 
}

Убедитесь, что вы прослушиваете сохранение контекстов управляемых объектов.

[[NSNotificationCenter defaultCenter] 
              addObserver:singleton 
                 selector:@selector(contextDidSave:)
                     name:NSManagedObjectContextDidSaveNotification object:nil];

В контекстеDidSave: вы сами сгенерируете изменение.

- (void) contextDidSave:(NSNotification*) notification
{
  if(![notification.object isEqual:self.mainContext]) {
    dispatch_async(dispatch_get_main_queue(), ^{
      [self.mainContext mergeChangesFromContextDidSaveNotification:notification];
    });
  }
}

Ответ 2

Контексты управляемых объектов не являются потокобезопасными, поэтому, если вам когда-либо понадобится выполнять какие-либо фоновые работы с вашими объектами Coredata (то есть с длительной функцией импорта/экспорта без блокировки основного пользовательского интерфейса), вы захотите сделать это на фоне нить.

В этих случаях вам нужно будет создать новый контекст управляемых объектов в фоновом потоке, выполнить итерацию в своей операции с coredata, а затем сообщить основной контекст ваших изменений.

Вы можете найти пример того, как это может работать здесь

Основные данные и потоки /Grand Central Dispatch