Pergunta : Como faço para que meu contexto filho veja as alterações persistentes no contexto pai para que acionem meu NSFetchedResultsController para atualizar a IU?
Aqui está a configuração:
Você tem um aplicativo que baixa e adiciona muitos dados XML (cerca de 2 milhões de registros, cada um com aproximadamente o tamanho de um parágrafo normal de texto). O arquivo .sqlite passa a ter cerca de 500 MB. Adicionar esse conteúdo ao Core Data leva tempo, mas você deseja que o usuário seja capaz de usar o aplicativo enquanto os dados são carregados no armazenamento de dados de forma incremental. Deve ser invisível e imperceptível para o usuário que grandes quantidades de dados estão sendo movidas, então sem travamentos, sem tremores: rola como manteiga. Ainda assim, o aplicativo é mais útil quanto mais dados são adicionados a ele, portanto, não podemos esperar para sempre que os dados sejam adicionados ao armazenamento de dados principais. No código, isso significa que eu realmente gostaria de evitar um código como este no código de importação:
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.25]];
O aplicativo é iOS 5 apenas, então o dispositivo mais lento que ele precisa para suportar é um iPhone 3GS.
Aqui estão os recursos que usei até agora para desenvolver minha solução atual:
Guia de programação de dados principais da Apple: importando dados com eficiência
- Use Autorelease Pools para manter a memória baixa
- Custo de relacionamento. Importe planos e, em seguida, remende os relacionamentos no final
- Não pergunte se você pode ajudar, isso retarda as coisas de uma maneira O (n ^ 2)
- Importar em lotes: salvar, redefinir, drenar e repetir
- Desligue o Undo Manager na importação
iDeveloper TV - Core Data Performance
- Use 3 contextos: tipos de contexto mestre, principal e de confinamento
iDeveloper TV - Atualização de dados principais para Mac, iPhone e iPad
- Executar salvamentos em outras filas com performBlock torna as coisas mais rápidas.
- A criptografia torna as coisas mais lentas, desligue-a se puder.
Importando e exibindo grandes conjuntos de dados em dados centrais por Marcus Zarra
- Você pode desacelerar a importação dando tempo ao loop de execução atual, para que as coisas pareçam tranquilas para o usuário.
- O código de amostra prova que é possível fazer grandes importações e manter a IU responsiva, mas não tão rápido quanto com 3 contextos e salvamento assíncrono em disco.
Minha Solução Atual
Eu tenho 3 instâncias de NSManagedObjectContext:
masterManagedObjectContext - Este é o contexto que possui o NSPersistentStoreCoordinator e é responsável por salvar em disco. Faço isso para que meus salvamentos possam ser assíncronos e, portanto, muito rápidos. Eu crio no lançamento assim:
masterManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[masterManagedObjectContext setPersistentStoreCoordinator:coordinator];
mainManagedObjectContext - Este é o contexto que a IU usa em todos os lugares. É um filho do masterManagedObjectContext. Eu crio assim:
mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[mainManagedObjectContext setUndoManager:nil];
[mainManagedObjectContext setParentContext:masterManagedObjectContext];
backgroundContext - Este contexto é criado em minha subclasse NSOperation que é responsável por importar os dados XML para Core Data. Eu o crio no método principal da operação e o vinculo ao contexto mestre lá.
backgroundContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];
[backgroundContext setUndoManager:nil];
[backgroundContext setParentContext:masterManagedObjectContext];
Isso realmente funciona muito, muito rápido. Apenas fazendo essa configuração de 3 contextos, consegui melhorar minha velocidade de importação em mais de 10 vezes! Honestamente, isso é difícil de acreditar. (Este design básico deve fazer parte do modelo padrão de dados principais ...)
Durante o processo de importação, salvo 2 formas diferentes. A cada 1000 itens que salvo no contexto de fundo:
BOOL saveSuccess = [backgroundContext save:&error];
Em seguida, no final do processo de importação, salvo no contexto principal / pai que, aparentemente, envia modificações para os outros contextos filho, incluindo o contexto principal:
[masterManagedObjectContext performBlock:^{
NSError *parentContextError = nil;
BOOL parentContextSaveSuccess = [masterManagedObjectContext save:&parentContextError];
}];
Problema : o problema é que minha IU não será atualizada até que eu recarregue a visualização.
Eu tenho um UIViewController simples com um UITableView que está sendo alimentado com dados usando um NSFetchedResultsController. Quando o processo de importação é concluído, o NSFetchedResultsController não vê nenhuma mudança no contexto pai / mestre e, portanto, a IU não é atualizada automaticamente como estou acostumado a ver. Se eu retirar o UIViewController da pilha e carregá-lo novamente, todos os dados estarão lá.
Pergunta : Como faço para que meu contexto filho veja as alterações persistentes no contexto pai para que acionem meu NSFetchedResultsController para atualizar a IU?
Eu tentei o seguinte, que apenas trava o aplicativo:
- (void)saveMasterContext {
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter addObserver:self selector:@selector(contextChanged:) name:NSManagedObjectContextDidSaveNotification object:masterManagedObjectContext];
NSError *error = nil;
BOOL saveSuccess = [masterManagedObjectContext save:&error];
[notificationCenter removeObserver:self name:NSManagedObjectContextDidSaveNotification object:masterManagedObjectContext];
}
- (void)contextChanged:(NSNotification*)notification
{
if ([notification object] == mainManagedObjectContext) return;
if (![NSThread isMainThread]) {
[self performSelectorOnMainThread:@selector(contextChanged:) withObject:notification waitUntilDone:YES];
return;
}
[mainManagedObjectContext mergeChangesFromContextDidSaveNotification:notification];
}