Основные данные и потоки / Grand Central Dispatch - PullRequest
40 голосов
/ 24 сентября 2011

Я новичок в Grand Central Dispatch (GCD) и Core Data, и мне нужна ваша помощь для использования Core Data с CGD, чтобы пользовательский интерфейс не блокировался, пока я добавляю 40 000 записей в Core Data.

Я знаю, что CD не является потокобезопасным, поэтому я должен использовать другой контекст, а затем сохранить данные и контексты слияния, насколько я смог понять из некоторых статей.

Что я не могпока не сделаю, это собрать кусочки.

Итак, в моем коде мне нужна ваша помощь, как это сделать.

У меня есть:

/*some other code*/

for (NSDictionary *memberData in arrayWithResult) {

    //get the Activities for this member
    NSArray *arrayWithMemberActivities = [activitiesDict objectForKey:[memberData objectForKey:@"MemberID"]];

    //create the Member, with the NSSet of Activities
    [Members createMemberWithDataFromServer:memberData
                         andActivitiesArray:arrayWithMemberActivities
                              andStaffArray:nil
                           andContactsArray:nil
                     inManagedObjectContext:self.managedObjectContext];
}

Как я могу преобразовать это для работы в фоновом режиме, а затем, после завершения сохранения, сохранить данные и обновить пользовательский интерфейс, не блокируя пользовательский интерфейс при сохранении 40 000 объектов?

Ответы [ 6 ]

57 голосов
/ 25 сентября 2011

Вот хороший пример для вас, чтобы попробовать. Не стесняйтесь возвращаться, если у вас есть какие-либо вопросы:

self.mainThreadContext... // This is a reference to your main thread context
NSPersistentStoreCoordinator *mainThreadContextStoreCoordinator = [self.mainThreadContext persistentStoreCoordinator];
dispatch_queue_t request_queue = dispatch_queue_create("com.yourapp.DescriptionOfMethod", NULL);
dispatch_async(request_queue, ^{

    // Create a new managed object context
    // Set its persistent store coordinator
    NSManagedObjectContext *newMoc = [[NSManagedObjectContext alloc] init];
    [newMoc setPersistentStoreCoordinator:mainThreadContextStoreCoordinator]];

    // Register for context save changes notification
    NSNotificationCenter *notify = [NSNotificationCenter defaultCenter];
    [notify addObserver:self 
               selector:@selector(mergeChanges:) 
                   name:NSManagedObjectContextDidSaveNotification 
                 object:newMoc];

    // Do the work
    // Your method here
    // Call save on context (this will send a save notification and call the method below)
    BOOL success = [newMoc save:&error];
    if (!success)
        // Deal with error
    [newMoc release];
});
dispatch_release(request_queue);

А в ответ на уведомление о сохранении контекста:

- (void)mergeChanges:(NSNotification*)notification 
{
    dispatch_async(dispatch_get_main_queue(), ^{
        [self.mainThreadContext mergeChangesFromContextDidSaveNotification:notification waitUntilDone:YES];
    });
}

И не забудьте удалить наблюдателя из центра уведомлений, как только вы покончили с контекстом фонового потока.

[[NSNotificationCenter defaultCenter] removeObserver:self];
8 голосов
/ 25 сентября 2011

Вот фрагмент, который охватывает GCD и пользовательский интерфейс в самых простых сроках.Вы можете заменить doWork своим кодом, который работает с CoreData.

Что касается безопасности CD и потоков, одна из приятных особенностей GCD - вы можете разделить области вашего приложения (подсистемы) для синхронизации и убедиться, что они получаютвыполняется в той же очереди.Вы можете выполнить всю работу CoreData в очереди с именем com.yourcompany.appname.dataaccess.

В этом примере есть кнопка, которая вызывает долгосрочную работу, метка состояния, и я добавил ползунок, чтобы показатьможно перемещать ползунок, пока работа bg завершена.

// on click of button
- (IBAction)doWork:(id)sender
{
    [[self feedbackLabel] setText:@"Working ..."];
    [[self doWorkButton] setEnabled:NO];

    // async queue for bg work
    // main queue for updating ui on main thread
    dispatch_queue_t queue = dispatch_queue_create("com.sample", 0);
    dispatch_queue_t main = dispatch_get_main_queue();

    //  do the long running work in bg async queue
    // within that, call to update UI on main thread.
    dispatch_async(queue, 
                   ^{ 
                       [self performLongRunningWork]; 
                       dispatch_async(main, ^{ [self workDone]; });
                   });

    // release queues created.
    dispatch_release(queue);    
}

- (void)performLongRunningWork
{
    // simulate 5 seconds of work
    // I added a slider to the form - I can slide it back and forth during the 5 sec.
    sleep(5);
}

- (void)workDone
{
    [[self feedbackLabel] setText:@"Done ..."];
    [[self doWorkButton] setEnabled:YES];
}
3 голосов
/ 25 сентября 2011

В этом блоге есть подробное описание параллелизма Core Data и пример кода: http://www.duckrowing.com/2010/03/11/using-core-data-on-multiple-threads/

1 голос
/ 22 июня 2013

Добавляя другой источник информации, вы можете проверить

ThreadedCoreData

Образец кода библиотеки Apple для разработчиков iOS, который был недавно обновлен (2013-06-09)

Демонстрирует, как использовать базовые данные в многопоточной среде, следуя первой рекомендованной схеме, упомянутой в Базовых данных Руководство по программированию.

На основе примера SeismicXML он загружает и анализирует RSS-канал Геологической службы США (USGS), которая предоставляет данные о недавние землетрясения по всему миру. Что отличает этот образец является то, что он постоянно хранит землетрясения с использованием основных данных. Каждый раз Вы запускаете приложение, оно загружает новые данные о землетрясениях, анализирует их в NSOperation, который проверяет дубликаты и хранит вновь основанные землетрясения как управляемые объекты.

Для новичков в Core Data может быть полезно сравнить SeismicXML образец с этим образцом и обратите внимание на необходимые ингредиенты для введите основные данные в ваше приложение.

0 голосов
/ 23 июля 2013

Гораздо проще сделать это, чем подключить постоянный координатор хранилища к новому контексту, который также не является поточно-ориентированным, кстати.

NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrency];
[context setParentContext:<main thread context here>];

[context performBlock:^{

    ...
    // Execute all code on current context
    ...

}];

NSError *error = nil;
[context save:&error];
if (!error) {
    [context.parentContext save:&error];
    if (error) {
        NSLog(@"Could not save parent context: %@", error);
    }
}
else {
    NSLog(@"Could not save context: %@", error);
}

Отличное руководство по использованию многоконтекстных базовых данных:

http://www.cocoanetics.com/2012/07/multi-context-coredata/

0 голосов
/ 23 июля 2013

Таким образом, выбранный ответ для этого - почти 2 года назад, и с ним есть несколько проблем:

  1. Это не дружественно к ARC - нужно удалить вызов релиза на newMoc - ARC даже не скомпилирует с этим
  2. Вы должны исполнять танец слабого самообмана / сильного самообмана внутри блока - иначе вы, вероятно, создаете цикл сохранения при создании наблюдателя. Смотрите документацию Apple здесь: http://developer.apple.com/library/ios/#documentation/cocoa/conceptual/ProgrammingWithObjectiveC/WorkingwithBlocks/WorkingwithBlocks.html
  3. @ RyanG спросил в комментарии, почему он блокирует. Я думаю, потому что недавно отредактированный метод имеет waitUntilDone: YES - за исключением того, что он блокирует основной поток. Вы, вероятно, хотите waitUntilDone: НЕТ, но я не знаю, будут ли обновляться пользовательские интерфейсы, которые также запускаются из этих событий изменений, поэтому потребуется тестирование.

- Изменить -

Заглядывая дальше в # 3 - waitUntilDone: ДА не является допустимой сигнатурой метода для объектов управляемого контекста, так как это вообще работает?

...