Pytanie : Jak sprawić, aby mój kontekst podrzędny zobaczył zmiany utrwalone w kontekście nadrzędnym, tak aby wyzwalały one mój NSFetchedResultsController w celu zaktualizowania interfejsu użytkownika?
Oto konfiguracja:
Masz aplikację, która pobiera i dodaje dużo danych XML (około 2 milionów rekordów, każdy mniej więcej rozmiar normalnego akapitu tekstu). Plik .sqlite ma rozmiar około 500 MB. Dodanie tej zawartości do danych podstawowych wymaga czasu, ale chcesz, aby użytkownik mógł korzystać z aplikacji, podczas gdy dane są ładowane do magazynu danych przyrostowo. Musi być niewidoczne i niezauważalne dla użytkownika, że przenoszone są duże ilości danych, więc nie zawiesza się, nie drży: zwoje jak masło. Mimo to aplikacja jest bardziej użyteczna, im więcej danych jest do niej dodawanych, więc nie możemy czekać w nieskończoność na dodanie danych do magazynu Core Data. W kodzie oznacza to, że naprawdę chciałbym uniknąć takiego kodu w kodzie importu:
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.25]];
Aplikacja działa tylko w systemie iOS 5, więc najwolniejszym urządzeniem, które musi obsługiwać, jest iPhone 3GS.
Oto zasoby, z których do tej pory korzystałem, aby opracować moje obecne rozwiązanie:
Podręcznik programowania podstawowych danych firmy Apple: wydajne importowanie danych
- Użyj puli Autorelease, aby zmniejszyć ilość pamięci
- Koszt relacji. Importuj na płasko, a na końcu popraw relacje
- Nie pytaj, czy możesz temu pomóc, spowalnia to wszystko w sposób O (n ^ 2)
- Importuj partiami: zapisz, zresetuj, opróżnij i powtórz
- Wyłącz Menedżera cofania podczas importu
iDeveloper TV - Wydajność podstawowych danych
- Użyj 3 kontekstów: kontekstów głównych, głównych i zamkniętych
iDeveloper TV - Aktualizacja podstawowych danych dla komputerów Mac, iPhone i iPad
- Uruchamianie zapisuje na innych kolejkach z performBlock przyspiesza pracę.
- Szyfrowanie spowalnia działanie, wyłącz je, jeśli możesz.
Importowanie i wyświetlanie dużych zestawów danych w danych podstawowych autorstwa Marcusa Zarry
- Możesz spowolnić import, dając czas na bieżącą pętlę uruchamiania, aby użytkownik czuł się płynnie.
- Przykładowy kod udowadnia, że można wykonywać duże importy i utrzymywać responsywny interfejs użytkownika, ale nie tak szybko, jak w przypadku 3 kontekstów i asynchronicznego zapisywania na dysku.
Moje obecne rozwiązanie
Mam 3 wystąpienia NSManagedObjectContext:
masterManagedObjectContext - jest to kontekst, który ma NSPersistentStoreCoordinator i jest odpowiedzialny za zapisywanie na dysku. Robię to, aby moje zapisy były asynchroniczne, a przez to bardzo szybkie. Tworzę go przy uruchomieniu w ten sposób:
masterManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[masterManagedObjectContext setPersistentStoreCoordinator:coordinator];
mainManagedObjectContext - jest to kontekst, którego interfejs użytkownika używa wszędzie. Jest elementem potomnym masterManagedObjectContext. Tworzę to tak:
mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[mainManagedObjectContext setUndoManager:nil];
[mainManagedObjectContext setParentContext:masterManagedObjectContext];
backgroundContext - ten kontekst jest tworzony w mojej podklasie NSOperation, która jest odpowiedzialna za importowanie danych XML do Core Data. Tworzę go w głównej metodzie operacji i łączę tam z głównym kontekstem.
backgroundContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];
[backgroundContext setUndoManager:nil];
[backgroundContext setParentContext:masterManagedObjectContext];
To faktycznie działa bardzo, BARDZO szybko. Po prostu wykonując tę konfigurację 3 kontekstów, udało mi się zwiększyć prędkość importu ponad 10 razy! Szczerze mówiąc, trudno w to uwierzyć. (Ten podstawowy projekt powinien być częścią standardowego szablonu podstawowych danych ...)
Podczas importu zapisuję 2 różne sposoby. Co 1000 pozycji zapisuję w kontekście tła:
BOOL saveSuccess = [backgroundContext save:&error];
Następnie pod koniec procesu importu zapisuję w kontekście głównym / nadrzędnym, który rzekomo wypycha modyfikacje do innych kontekstów potomnych, w tym głównego kontekstu:
[masterManagedObjectContext performBlock:^{
NSError *parentContextError = nil;
BOOL parentContextSaveSuccess = [masterManagedObjectContext save:&parentContextError];
}];
Problem : Problem polega na tym, że mój interfejs użytkownika nie zaktualizuje się, dopóki ponownie nie załaduję widoku.
Mam prosty UIViewController z UITableView, który jest podawany danych przy użyciu NSFetchedResultsController. Po zakończeniu procesu importu NSFetchedResultsController nie widzi żadnych zmian z kontekstu nadrzędnego / głównego, a więc interfejs użytkownika nie aktualizuje się automatycznie, tak jak zwykle. Jeśli zdejmę UIViewController ze stosu i załaduję go ponownie, wszystkie dane tam są.
Pytanie : Jak sprawić, aby mój kontekst podrzędny zobaczył zmiany utrwalone w kontekście nadrzędnym, tak aby wyzwalały one mój NSFetchedResultsController w celu zaktualizowania interfejsu użytkownika?
Wypróbowałem następujące rozwiązanie, które po prostu zawiesza aplikację:
- (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];
}