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];
}
Odpowiedzi:
Prawdopodobnie powinieneś również szybko zapisać nadrzędny MOC. Nie ma sensu, aby MOC czekał do końca, aby zapisać. Ma swój własny wątek i pomoże również utrzymać pamięć.
Napisałeś:
W twojej konfiguracji masz dwoje dzieci (główny MOC i MOC działający w tle), oba są rodzicami „mastera”.
Kiedy oszczędzasz na dziecku, wypycha zmiany do rodzica. Inne elementy podrzędne tego MOC zobaczą dane przy następnym pobieraniu ... nie są jawnie powiadamiane.
Tak więc, kiedy BG zapisuje, jego dane są przesyłane do MASTER. Należy jednak pamiętać, że żadne z tych danych nie znajdują się na dysku, dopóki MASTER nie zapisze. Co więcej, żadne nowe elementy nie otrzymają trwałych identyfikatorów, dopóki MASTER nie zapisze ich na dysku.
W twoim scenariuszu pobierasz dane do MAIN MOC poprzez scalenie z MASTER save podczas powiadomienia DidSave.
To powinno zadziałać, więc jestem ciekawy, gdzie jest „zawieszony”. Zaznaczę, że nie uruchamiasz się na głównym wątku MOC w sposób kanoniczny (przynajmniej nie na iOS 5).
Ponadto prawdopodobnie jesteś zainteresowany tylko scaleniem zmian z głównego MOC (chociaż twoja rejestracja wygląda tak, jakby była tylko do tego). Gdybym miał użyć powiadomienia o aktualizacji przy zapisywaniu, zrobiłbym to ...
Teraz, co może być twoim prawdziwym problemem związanym z zawieszaniem się ... pokazujesz dwa różne wywołania, aby zapisać na master. pierwsza jest dobrze chroniona w swoim własnym performBlock, ale druga nie (chociaż możesz wywoływać saveMasterContext w performBlock ...
Jednak zmieniłbym też ten kod ...
Należy jednak pamiętać, że MAIN jest dzieckiem MASTER. Więc nie powinno być konieczne scalanie zmian. Zamiast tego po prostu obserwuj DidSave na master i po prostu ponów! Dane są już w Twoim rodzicu i tylko czekają, aż o nie poprosisz. To przede wszystkim jedna z zalet posiadania danych u rodzica.
Kolejna alternatywa do rozważenia (a chciałbym usłyszeć o twoich wynikach - to dużo danych) ...
Zamiast uczynić tło MOC dzieckiem MISTRZA, uczyń go dzieckiem GŁÓWNEGO.
Uzyskać to. Za każdym razem, gdy BG zapisuje, jest automatycznie wpychany do GŁÓWNEGO. Teraz MAIN musi wywołać save, a następnie master musi wywołać save, ale wszystko co robią to przesuwanie wskaźników ... aż master zapisze na dysk.
Piękno tej metody polega na tym, że dane są przesyłane z MOC w tle bezpośrednio do MOC aplikacji (a następnie przechodzą do zapisania).
Jest pewna kara za przejście, ale całe ciężkie podnoszenie jest wykonywane w MASTER, gdy uderza w dysk. A jeśli wyrzucisz te zapisy z mastera za pomocą performBlock, główny wątek po prostu wyśle żądanie i natychmiast wróci.
Daj mi znać, jak leci!
źródło