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];
}
Respostas:
Você provavelmente também deve salvar o MOC mestre em passos largos. Não faz sentido ter aquele MOC esperando até o fim para salvar. Ele tem seu próprio thread e também ajudará a manter a memória baixa.
Você escreveu:
Em sua configuração, você tem dois filhos (o MOC principal e o MOC de fundo), ambos sendo pais do "mestre".
Quando você salva em uma criança, isso empurra as mudanças para o pai. Outros filhos desse MOC verão os dados na próxima vez que executarem uma busca ... eles não são notificados explicitamente.
Portanto, quando o BG salva, seus dados são enviados para MASTER. Observe, no entanto, que nenhum desses dados está no disco até que o MASTER seja salvo. Além disso, quaisquer novos itens não obterão IDs permanentes até que o MASTER salve no disco.
Em seu cenário, você está puxando os dados para o MAIN MOC mesclando do MASTER salvar durante a notificação DidSave.
Isso deve funcionar, então estou curioso para saber onde está "pendurado". Notarei que você não está executando no thread principal do MOC de forma canônica (pelo menos não para iOS 5).
Além disso, você provavelmente está interessado apenas em mesclar as alterações do MOC mestre (embora pareça que seu registro é apenas para isso). Se eu fosse usar a notificação de atualização ao salvar, faria isso ...
Agora, qual pode ser o seu verdadeiro problema com relação ao travamento ... você mostra duas chamadas diferentes para salvar no master. o primeiro está bem protegido em seu próprio performBlock, mas o segundo não (embora você possa estar chamando saveMasterContext em um performBlock ...
No entanto, eu também alteraria este código ...
No entanto, observe que o MAIN é filho de MASTER. Portanto, não deve ser necessário mesclar as alterações. Em vez disso, observe o DidSave no mestre e recupere! Os dados já estão guardados em seus pais, apenas esperando que você os peça. Esse é um dos benefícios de ter os dados no pai em primeiro lugar.
Outra alternativa a ser considerada (e estou interessado em ouvir sobre seus resultados - são muitos dados) ...
Em vez de tornar o MOC de fundo um filho do MASTER, torne-o filho do MAIN.
Pegue isto. Cada vez que o BG salva, ele é automaticamente colocado no MAIN. Agora, o MAIN tem que chamar save, e o mestre tem que chamar save, mas tudo o que está fazendo é mover ponteiros ... até que o mestre salve no disco.
A beleza desse método é que os dados vão do MOC de segundo plano diretamente para o MOC de seus aplicativos (em seguida, passam para serem salvos).
Há alguma penalidade para o repasse, mas todo o trabalho pesado é feito no MASTER quando atinge o disco. E se você chutar esses salvamentos no master com performBlock, então o thread principal apenas envia a solicitação e retorna imediatamente.
Por favor, deixe-me saber como foi!
fonte