Основные данные и потоки / Grand Central Dispatch
Я новичок в 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 ответов
Вот хороший пример для вас, чтобы попробовать. Не стесняйтесь возвращаться, если у вас есть какие-либо вопросы:
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];
Вот фрагмент, который охватывает 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];
}
Этот блог содержит подробное описание параллелизма Core Data и пример кода: http://www.duckrowing.com/2010/03/11/using-core-data-on-multiple-threads/
Добавляя другой источник информации, вы можете проверить
пример кода библиотеки Apple для разработчиков iOS, который был недавно обновлен (2013-06-09)
Демонстрирует, как использовать Core Data в многопоточной среде, следуя первой рекомендованной схеме, упомянутой в Руководстве по программированию Core Data.
На основе образца SeismicXML он загружает и анализирует RSS-канал из Геологической службы США (USGS), который предоставляет данные о недавних землетрясениях по всему миру. Отличительной чертой этого образца является то, что он постоянно хранит землетрясения с использованием базовых данных. Каждый раз, когда вы запускаете приложение, оно загружает новые данные о землетрясениях, анализирует их в NSOperation, который проверяет дубликаты и сохраняет вновь обнаруженные землетрясения как управляемые объекты.
Для новичков в Core Data может быть полезно сравнить образец SeismicXML с этим образцом и отметить необходимые ингредиенты для внедрения Core Data в ваше приложение.
Таким образом, выбранный ответ для этого - почти 2 года назад, и с этим есть несколько проблем:
- Это не дружественно к ARC - нужно удалить вызов релиза на newMoc - ARC даже не скомпилируется с этим
- Вы должны выполнять танец слабый-сильный / сильный-внутренний внутри блока - в противном случае вы, вероятно, создаете цикл сохранения при создании наблюдателя. См. Документацию Apple здесь: http://developer.apple.com/library/ios/#documentation/cocoa/conceptual/ProgrammingWithObjectiveC/WorkingwithBlocks/WorkingwithBlocks.html
- @RyanG спросил в комментарии, почему он блокирует. Я думаю, потому что недавно отредактированный метод имеет waitUntilDone:YES - за исключением того, что он блокирует основной поток. Вы, вероятно, хотите waitUntilDone: НЕТ, но я не знаю, будут ли обновляться пользовательские интерфейсы с этих событий изменений, так что это потребует тестирования.
--Редактировать--
Подробнее о #3 - waitUntilDone: ДА не является допустимым methodS ignature для объектов управляемого контекста, так как это вообще работает?
Гораздо более простой способ сделать это, чем подключить постоянный координатор хранилища к новому контексту, который также не является поточно-ориентированным, кстати.
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);
}
Отличный учебник по использованию многоконтекстных базовых данных: