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

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

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

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

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

У меня есть:

/*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 объектов?


person Rui Lopes    schedule 24.09.2011    source источник


Ответы (6)


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

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];
person Rog    schedule 25.09.2011
comment
отличный. Спасибо. Немножко ');' отсутствует перед dispatch_release (request_queue). Спасибо. - person Rui Lopes; 25.09.2011
comment
не следует ли нам удалить наблюдателя после выпуска newMOC? - person Rui Lopes; 25.09.2011
comment
да, это звучит как хорошая идея. У меня есть вспомогательный метод, в котором я оборачиваю свои задачи фоновой обработки, чтобы наблюдатель обычно удалялся при освобождении этого класса. - person Rog; 26.09.2011
comment
Итак, вы говорите, что в dealloc я должен удалить его следующим образом: [[NSNotificationCenter defaultCenter] removeObserver: self]; Можете ли вы обновить свой ответ, чтобы он был понятен другим при просмотре? - person Rui Lopes; 26.09.2011
comment
@Rog есть ли новый / лучший способ сделать это? Я использовал ваш код, но мой пользовательский интерфейс все еще блокируется - я тоже изучил MagicalRecord, независимо от того, какой у меня пользовательский интерфейс заблокирован. - person RyanG; 08.01.2013
comment
Мои мысли по этому поводу см. В моем ответе ниже. - person Matt S.; 23.07.2013

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

Что касается безопасности компакт-дисков и потоков, одна из приятных частей 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];
}
person bryanmac    schedule 25.09.2011
comment
ваш пример классный, но не указывает совпадение основных данных. Спасибо, в любом случае. - person Rui Lopes; 25.09.2011
comment
Дело в том, что очередь обрабатывает параллелизм, если вы разделяете подсистемы в своем приложении и убедитесь, что вся асинхронная работа, поставленная в очередь для этой подсистемы, использует одну и ту же очередь. - person bryanmac; 26.09.2011
comment
Из сообщения выше: Что касается безопасности компакт-дисков и потоков, одна из приятных частей GCD - это то, что вы можете разделять области вашего приложения (подсистемы) для синхронизации и гарантировать, что они будут выполняться в одной очереди. Вы можете выполнить всю работу CoreData в очереди с именем com.yourcompany.appname.dataaccess. - person bryanmac; 26.09.2011
comment
@bryanmac +1 для примера того, как получить ссылку на основной поток для обновлений пользовательского интерфейса. Также не забудьте освободить очередь, поскольку вы создали ее сами с помощью dispatch_queue_create. - person Rog; 26.09.2011
comment
обновляется с выпуском в коде, а примечание о параллелизме находится в публикации. - person bryanmac; 27.09.2011

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

person Michael    schedule 25.09.2011

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

ThreadedCoreData

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

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

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

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

person angelos.p    schedule 22.06.2013

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

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

--Редактировать--

Заглянем далее в № 3 - waitUntilDone: YES не является допустимым methodSignature для объектов управляемого контекста, так как же это вообще работает?

person Matt S.    schedule 23.07.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);
}

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

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

person Simon Germain    schedule 23.07.2013