Wdrożenie szybkiego i wydajnego importu danych podstawowych w systemie iOS 5

Pytanie: Jak sprawić, by mój kontekst dziecka widział zmiany utrzymujące się w kontekście rodzica, aby wyzwalały Kontroler nsfetchedresultscontroller do aktualizacji interfejsu użytkownika?

Oto konfiguracja:

Masz aplikację, która pobiera i dodaje wiele danych XML (około 2 milionów rekordów, każdy mniej więcej wielkości normalnego akapitu tekstu).plik SQLITE ma rozmiar około 500 MB. Dodanie tej treści do podstawowych danych wymaga czasu, ale chcesz, aby użytkownik mógł korzystaj z aplikacji, gdy dane są stopniowo ładowane do magazynu danych. To musi być niewidoczne i niezauważalne dla użytkownika, że duże ilości danych są przenoszone, więc nie zawiesza się, nie trema: scrolls jak masło. Mimo to aplikacja jest bardziej przydatna, im więcej danych jest do niej dodawanych, więc nie możemy czekać wiecznie, aż dane zostaną dodane do podstawowego magazynu danych. W kodzie oznacza to, że naprawdę chciałbym uniknąć kodu takiego w kodzie importu:

[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.25]];

Aplikacja jest tylko iOS 5, więc najwolniejsze urządzenie musi obsługiwać iPhone 3GS.

Oto zasoby, z których dotychczas korzystałem, aby opracować moje obecne rozwiązanie:

Apple ' s Core Data Programming Guide: wydajny Import danych

  • użyj Pul Autorelease, aby utrzymać pamięć w dół
  • Relacje Kosztują. Importuj flat, a następnie łataj relacje na końcu
  • Nie pytaj, czy możesz mu pomóc, spowalnia wszystko w sposób O (n^2)]}
  • Import w partiach: save, reset, drain i powtórz
  • Wyłącz Menedżer cofania przy imporcie

IDeveloper TV-Core Data Performance

  • Użyj 3 kontekstów: głównego, głównego i ograniczonego typu kontekstu

IDeveloper TV-aktualizacja danych dla komputerów Mac, iPhone i iPad

  • uruchamianie zapisów w innych kolejkach z performBlock sprawia, że wszystko jest szybkie.
  • Szyfrowanie spowalnia wszystko, wyłącz to, jeśli możesz.

Importowanie i wyświetlanie Duże zbiory danych w danych podstawowych Marcus Zarra

  • możesz spowolnić import, dając czas bieżącej pętli run, więc rzeczy czują się gładkie dla użytkownika.
  • przykładowy kod udowadnia, że możliwe jest wykonywanie dużych importów i utrzymanie responsywności interfejsu użytkownika, ale nie tak szybko jak w przypadku 3 kontekstów i asynchronicznego zapisywania na dysku.

Moje Obecne Rozwiązanie

Mam 3 instancje NSManagedObjectContext:

MasterManagedObjectContext - to jest kontekst, który ma NSPersistentStoreCoordinator i jest odpowiedzialny za zapis na dysku. Robię to, aby moje zapisy mogły być asynchroniczne, a tym samym bardzo szybkie. Tworzę go przy starcie tak:

masterManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[masterManagedObjectContext setPersistentStoreCoordinator:coordinator];

MainManagedObjectContext - jest to kontekst, którego UI używa wszędzie. To jest dziecko masterManagedObjectContext. Tworzę tak:

mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[mainManagedObjectContext setUndoManager:nil];
[mainManagedObjectContext setParentContext:masterManagedObjectContext];

BackgroundContext - ten kontekst jest tworzony w mojej podklasie NSOperation, która odpowiada za importowanie Dane XML do danych podstawowych. Tworzę go w głównej metodzie operacji i linkuję do głównego kontekstu.

backgroundContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];
[backgroundContext setUndoManager:nil];
[backgroundContext setParentContext:masterManagedObjectContext];
To naprawdę działa bardzo, bardzo szybko. Po prostu robiąc tę konfigurację kontekstu 3 byłem w stanie poprawić szybkość importu o ponad 10x! Szczerze, trudno w to uwierzyć. (Ten podstawowy projekt powinien być częścią standardowego szablonu danych podstawowych...)

Podczas importowania zapisuję na 2 różne sposoby. Co 1000 pozycji zapisuję w kontekście tła:

BOOL saveSuccess = [backgroundContext save:&error];

Wtedy pod koniec procesu importowania zapisuję w kontekście nadrzędnym / nadrzędnym, który pozornie 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 nie przeładuję widoku.

Mam prosty UIViewController z UITableView, który jest zasilany danymi za pomocą kontrolera NSFetchedResultsController. Po zakończeniu procesu importu, Kontroler nsfetchedresultscontroller nie widzi żadnych zmian z kontekst nadrzędny/główny i tak interfejs użytkownika nie aktualizuje się automatycznie, jak jestem przyzwyczajony do oglądania. Jeśli zdejmę Kontroler UIViewController ze stosu i załaduję go ponownie, wszystkie dane tam są.

Pytanie: Jak sprawić, by mój kontekst dziecka widział zmiany utrzymujące się w kontekście rodzica, aby wyzwalały Kontroler nsfetchedresultscontroller do aktualizacji interfejsu użytkownika?

Próbowałem następujących, 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];
}
Author: David Weiss, 2012-05-11

1 answers

Prawdopodobnie powinieneś uratować mistrza MOC w krokach, jak również. Nie ma sensu czekać na ratunek do końca. Ma swój własny wątek i pomoże zachować pamięć w dół, jak również.

Napisałeś:

Następnie pod koniec procesu importowania zapisuję NA master / parent kontekstu, który pozornie wypycha modyfikacje na drugie dziecko konteksty łącznie z głównym kontekstem:

W Twojej konfiguracji masz dwoje dzieci (główny MOC i tło MOC), oboje wychowywani do " mistrza."

Kiedy oszczędzasz na dziecku, przesuwa zmiany do rodzica. Inne Dzieci tego MOC zobaczą dane następnym razem, gdy wykonają pobieranie... nie są one wyraźnie zgłaszane.

Tak więc, gdy BG zapisuje, jego dane są wypychane do mastera. Należy jednak pamiętać, że żadne z tych danych nie znajduje się na dysku, dopóki MASTER nie zapisze. Co więcej, nowe elementy nie otrzymają stałych identyfikatorów, dopóki MASTER nie zapisze ich na dysku.

W Twoim scenariuszu, jesteś pobieranie danych do głównego MOC poprzez połączenie z głównego zapisu podczas powiadomienia DidSave.

To powinno zadziałać, więc jestem ciekaw, gdzie to jest " wisiał."Zauważę, że nie działasz na głównym wątku MOC w sposób kanoniczny (przynajmniej nie dla iOS 5).

Również, prawdopodobnie jesteś zainteresowany tylko scalanie zmian z master MOC (choć Twoja rejestracja wygląda tak, że tylko do tego). Gdybym miał użyć update-on-did-save-notification, zrobiłbym to...

- (void)contextChanged:(NSNotification*)notification {
    // Only interested in merging from master into main.
    if ([notification object] != masterManagedObjectContext) return;

    [mainManagedObjectContext performBlock:^{
        [mainManagedObjectContext mergeChangesFromContextDidSaveNotification:notification];

        // NOTE: our MOC should not be updated, but we need to reload the data as well
    }];
}
A teraz, co może być twoim prawdziwym problemem w związku z powieszeniem... pokazujesz dwa różne połączenia, aby zaoszczędzić na mistrzu. pierwszy jest dobrze chroniony we własnym bloku performBlock, ale drugi nie jest (chociaż możesz wywoływać saveMasterContext w bloku performBlock...

Jednak zmieniłbym również ten kod...

- (void)saveMasterContext {
    NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];    
    [notificationCenter addObserver:self selector:@selector(contextChanged:) name:NSManagedObjectContextDidSaveNotification object:masterManagedObjectContext];

    // Make sure the master runs in it's own thread...
    [masterManagedObjectContext performBlock:^{
        NSError *error = nil;
        BOOL saveSuccess = [masterManagedObjectContext save:&error];
        // Handle error...
        [notificationCenter removeObserver:self name:NSManagedObjectContextDidSaveNotification object:masterManagedObjectContext];
    }];
}

Zauważ jednak, że główny jest dzieckiem mistrza. Nie powinno więc scalać zmian. Zamiast tego, po prostu uważaj na DidSave na mistrza, a po prostu refetch! Dane są już w Twoim rodzicu i czekają, aż o nie poprosisz. To jedna z zalet posiadania danych w rodzicu.

Kolejna alternatywa do rozważenia(i chciałbym usłyszeć o twoich wynikach -- to dużo danych)...

Zamiast robić z tła dziecko mistrza, zrób z niego dziecko głównego.

Posłuchaj tego. Za każdym razem, gdy BG zapisuje, automatycznie zostaje wciśnięty do głównego. Teraz, MAIN musi wywołać save, a następnie master musi wywołać save, ale wszystko, co robią, to ruchome wskaźniki... dopóki master nie zapisze na dysk.

Piękno tej metody jest to, że dane przechodzi z Tło MOC prosto do aplikacji MOC (następnie przechodzi, aby zapisać).

Jestjakaś kara za przejście, ale wszystkie ciężkie podnoszenie odbywa się w MASTER, gdy uderza w dysk. A jeśli kopniesz te save ' y na master z performBlock, następnie główny wątek po prostu wysyła żądanie i natychmiast powraca.

Proszę dać mi znać, jak to idzie!

 46
Author: Jody Hagins,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-07-15 12:47:26