Domanda : come faccio a far sì che il mio contesto figlio veda le modifiche persistenti nel contesto padre in modo che attivino il mio NSFetchedResultsController per aggiornare l'interfaccia utente?
Ecco la configurazione:
Hai un'app che scarica e aggiunge molti dati XML (circa 2 milioni di record, ciascuno delle dimensioni di un normale paragrafo di testo) Il file .sqlite diventa di circa 500 MB. L'aggiunta di questo contenuto a Core Data richiede tempo, ma si desidera che l'utente sia in grado di utilizzare l'app mentre i dati vengono caricati nell'archivio dati in modo incrementale. Deve essere invisibile e impercettibile all'utente che grandi quantità di dati vengano spostati, quindi niente blocchi, niente nervosismo: scorre come il burro. Tuttavia, l'app è più utile, più dati vengono aggiunti, quindi non possiamo aspettare per sempre che i dati vengano aggiunti al Core Data Store. Nel codice questo significa che mi piacerebbe davvero evitare codice come questo nel codice di importazione:
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.25]];
L'app è solo iOS 5, quindi il dispositivo più lento che deve supportare è un iPhone 3GS.
Ecco le risorse che ho utilizzato finora per sviluppare la mia soluzione attuale:
Guida alla programmazione dei dati principali di Apple: importazione efficiente dei dati
- Usa i pool di rilascio automatico per mantenere bassa la memoria
- Costo delle relazioni. Importa flat, quindi aggiusta le relazioni alla fine
- Non interrogare se puoi evitarlo, rallenta le cose in modo O (n ^ 2)
- Importa in batch: salva, ripristina, scarica e ripeti
- Disattiva Gestione annullamenti durante l'importazione
iDeveloper TV: prestazioni dei dati fondamentali
- Usa 3 contesti: tipi di contesto principale, principale e confinato
iDeveloper TV - Aggiornamento dati principali per Mac, iPhone e iPad
- L'esecuzione di salvataggi su altre code con performBlock rende le cose veloci.
- La crittografia rallenta le cose, disattivala se puoi.
- Puoi rallentare l'importazione dando tempo al ciclo di esecuzione corrente, in modo che le cose sembrino fluide per l'utente.
- Il codice di esempio dimostra che è possibile eseguire grandi importazioni e mantenere reattiva l'interfaccia utente, ma non così velocemente come con 3 contesti e salvataggio asincrono su disco.
La mia soluzione attuale
Ho 3 istanze di NSManagedObjectContext:
masterManagedObjectContext - Questo è il contesto che ha NSPersistentStoreCoordinator ed è responsabile del salvataggio su disco. Lo faccio in modo che i miei salvataggi possano essere asincroni e quindi molto veloci. Lo creo al lancio in questo modo:
masterManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[masterManagedObjectContext setPersistentStoreCoordinator:coordinator];
mainManagedObjectContext : questo è il contesto che l'interfaccia utente utilizza ovunque. È un figlio di masterManagedObjectContext. Lo creo così:
mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[mainManagedObjectContext setUndoManager:nil];
[mainManagedObjectContext setParentContext:masterManagedObjectContext];
backgroundContext : questo contesto viene creato nella mia sottoclasse NSOperation che è responsabile dell'importazione dei dati XML in Core Data. Lo creo nel metodo principale dell'operazione e lo collego al contesto master lì.
backgroundContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];
[backgroundContext setUndoManager:nil];
[backgroundContext setParentContext:masterManagedObjectContext];
Funziona in realtà molto, MOLTO veloce. Solo eseguendo questa configurazione in 3 contesti sono stato in grado di migliorare la mia velocità di importazione di oltre 10 volte! Onestamente, questo è difficile da credere. (Questo design di base dovrebbe far parte del modello Core Data standard ...)
Durante il processo di importazione salvo 2 modi diversi. Ogni 1000 elementi che salvo nel contesto di sfondo:
BOOL saveSuccess = [backgroundContext save:&error];
Quindi, alla fine del processo di importazione, salvo sul contesto principale / genitore che, apparentemente, spinge le modifiche agli altri contesti figlio incluso il contesto principale:
[masterManagedObjectContext performBlock:^{
NSError *parentContextError = nil;
BOOL parentContextSaveSuccess = [masterManagedObjectContext save:&parentContextError];
}];
Problema : il problema è che la mia interfaccia utente non si aggiornerà finché non ricaricherò la visualizzazione.
Ho un semplice UIViewController con un UITableView che riceve dati utilizzando un NSFetchedResultsController. Al termine del processo di importazione, NSFetchedResultsController non vede modifiche dal contesto padre / master e quindi l'interfaccia utente non si aggiorna automaticamente come sono abituato a vedere. Se estraggo l'UIViewController dallo stack e lo carico di nuovo, tutti i dati sono lì.
Domanda : come faccio a far sì che il mio contesto figlio veda le modifiche persistenti nel contesto padre in modo che attivino il mio NSFetchedResultsController per aggiornare l'interfaccia utente?
Ho provato quanto segue che blocca l'app:
- (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];
}