Przykład lub wyjaśnienie migracji podstawowych danych z wieloma przejściami?

85

Moja aplikacja na iPhone'a wymaga migracji podstawowego magazynu danych, a niektóre bazy danych są dość duże. Dokumentacja Apple sugeruje użycie „wielu przebiegów” do migracji danych w celu zmniejszenia zużycia pamięci. Jednak dokumentacja jest bardzo ograniczona i nie wyjaśnia zbyt dobrze, jak to zrobić. Czy ktoś może wskazać mi dobry przykład lub szczegółowo wyjaśnić proces, w jaki sposób to osiągnąć?

Jason
źródło
czy faktycznie masz problemy z pamięcią? Czy Twoja migracja jest niewielka, czy chcesz używać NSMigrationManager?
Nick Weaver,
Tak, konsola GDB pokazała, że ​​były ostrzeżenia dotyczące pamięci, a następnie aplikacja ulega awarii z powodu ograniczonej pamięci. Próbowałem zarówno lekkiej migracji, jak i NSMigrationManager, ale teraz próbuję użyć NSMigrationManager.
Jason
ok, czy możesz bardziej szczegółowo opisać, co się zmieniło?
Nick Weaver,
wreszcie się dowiedziałem, przeczytaj moją odpowiedź.
Nick Weaver
Cześć Jason, czy mógłbyś naprawić podobny problem w pytaniu?
Yuchen Zhong

Odpowiedzi:

174

Dowiedziałem się, co podpowiada Apple w ich dokumentacji . W rzeczywistości jest to bardzo łatwe, ale długa droga, zanim stanie się to oczywiste. Zilustruję wyjaśnienie na przykładzie. Sytuacja początkowa jest następująca:

Wersja modelu danych 1

wprowadź opis obrazu tutaj wprowadź opis obrazu tutaj

Jest to model, który otrzymujesz podczas tworzenia projektu za pomocą szablonu „aplikacja oparta na nawigacji z podstawowym magazynem danych”. Skompilowałem go i zrobiłem trochę mocnego uderzenia z pomocą pętli for, aby utworzyć około 2k wpisów, wszystkie z różnymi wartościami. Idziemy do 2.000 zdarzeń z wartością NSDate.

Teraz dodajemy drugą wersję modelu danych, która wygląda następująco:

wprowadź opis obrazu tutaj

Wersja modelu danych 2

Różnica polega na tym, że jednostka Event zniknęła, a mamy dwie nowe. Jeden, który przechowuje znacznik czasu jako a, doublea drugi, który powinien przechowywać datę jako NSString.

Celem jest przeniesienie wszystkich zdarzeń wersji 1 do dwóch nowych jednostek i konwersja wartości podczas migracji. Powoduje to dwukrotność wartości, z których każda jest innego typu w oddzielnej jednostce.

Aby przeprowadzić migrację, wybieramy migrację ręcznie i robimy to z mapowaniem modeli. To także pierwsza część odpowiedzi na Twoje pytanie. Migrację wykonamy w dwóch krokach, ponieważ migracja 2k wpisów zajmuje dużo czasu, a my chcemy, aby zużycie pamięci było niskie.

Możesz nawet dalej podzielić te modele mapowania, aby migrować tylko zakresy jednostek. Powiedzmy, że mamy milion rekordów, to może zawiesić cały proces. Możliwe jest zawężenie pobieranych jednostek za pomocą predykatu filtru .

Wróćmy do naszych dwóch modeli mapowania.

Pierwszy model mapowania tworzymy w ten sposób:

1. Nowy plik -> Zasób -> Model mapowania wprowadź opis obrazu tutaj

2. Wybierz nazwę, wybrałem StepOne

3. Ustaw źródłowy i docelowy model danych

wprowadź opis obrazu tutaj

Mapowanie modelu - krok pierwszy

wprowadź opis obrazu tutaj

wprowadź opis obrazu tutaj

wprowadź opis obrazu tutaj

Migracja wieloprzebiegowa nie wymaga niestandardowych zasad migracji encji, jednak zrobimy to, aby uzyskać więcej szczegółów w tym przykładzie. Więc dodajemy niestandardowe zasady do encji. To jest zawsze podklasa NSEntityMigrationPolicy.

wprowadź opis obrazu tutaj

Ta klasa strategii implementuje pewne metody, aby dokonać migracji. Jednak to proste w tym przypadku tak będziemy musieli realizować tylko jedną metodę: createDestinationInstancesForSourceInstance:entityMapping:manager:error:.

Implementacja będzie wyglądać następująco:

StepOneEntityMigrationPolicy.m

#import "StepOneEntityMigrationPolicy.h"


@implementation StepOneEntityMigrationPolicy

- (BOOL)createDestinationInstancesForSourceInstance:(NSManagedObject *)sInstance 
                                      entityMapping:(NSEntityMapping *)mapping 
                                            manager:(NSMigrationManager *)manager 
                                              error:(NSError **)error
{
    // Create a new object for the model context
    NSManagedObject *newObject = 
        [NSEntityDescription insertNewObjectForEntityForName:[mapping destinationEntityName] 
                                      inManagedObjectContext:[manager destinationContext]];

    // do our transfer of nsdate to nsstring
    NSDate *date = [sInstance valueForKey:@"timeStamp"];
    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
    [dateFormatter setTimeStyle:NSDateFormatterMediumStyle];
    [dateFormatter setDateStyle:NSDateFormatterMediumStyle];    

    // set the value for our new object
    [newObject setValue:[dateFormatter stringFromDate:date] forKey:@"printedDate"];
    [dateFormatter release];

    // do the coupling of old and new
    [manager associateSourceInstance:sInstance withDestinationInstance:newObject forEntityMapping:mapping];

    return YES;
}

Ostatni krok: sama migracja

Pominię część dotyczącą konfigurowania drugiego modelu mapowania, który jest prawie identyczny, tylko timeIntervalSince1970 użyty do konwersji NSDate na podwójną.

Wreszcie musimy uruchomić migrację. Na razie pominę kod standardowy. Jeśli potrzebujesz, napiszę tutaj. Można go znaleźć na stronie Dostosowywanie procesu migracji, to tylko połączenie dwóch pierwszych przykładów kodu. Trzecia i ostatnia część zostanie zmodyfikowana w następujący sposób: Zamiast używać metody NSMappingModelklasy mappingModelFromBundles:forSourceModel:destinationModel:, użyjemy metody, initWithContentsOfURL:ponieważ metoda klasowa zwróci tylko jeden, być może pierwszy, znaleziony model mapowania w pakiecie.

Teraz mamy dwa modele mapowania, których można użyć w każdym przejściu pętli i wysłać metodę migracji do menedżera migracji. Otóż ​​to.

NSArray *mappingModelNames = [NSArray arrayWithObjects:@"StepOne", @"StepTwo", nil];
NSDictionary *sourceStoreOptions = nil;

NSURL *destinationStoreURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"CoreDataMigrationNew.sqlite"];

NSString *destinationStoreType = NSSQLiteStoreType;

NSDictionary *destinationStoreOptions = nil;

for (NSString *mappingModelName in mappingModelNames) {
    NSURL *fileURL = [[NSBundle mainBundle] URLForResource:mappingModelName withExtension:@"cdm"];

    NSMappingModel *mappingModel = [[NSMappingModel alloc] initWithContentsOfURL:fileURL];

    BOOL ok = [migrationManager migrateStoreFromURL:sourceStoreURL
                                               type:sourceStoreType
                                            options:sourceStoreOptions
                                   withMappingModel:mappingModel
                                   toDestinationURL:destinationStoreURL
                                    destinationType:destinationStoreType
                                 destinationOptions:destinationStoreOptions
                                              error:&error2];
    [mappingModel release];
} 

Uwagi

  • Model mapowania kończy się w cdmpakiecie.

  • Należy podać magazyn docelowy i nie powinien to być magazyn źródłowy. Po udanej migracji możesz usunąć starą i zmienić nazwę nowej.

  • Dokonałem pewnych zmian w modelu danych po utworzeniu modeli mapujących, co spowodowało pewne błędy kompatybilności, które mogłem rozwiązać tylko odtwarzając modele mapowania.

Nick Weaver
źródło
59
Cholera, to skomplikowane. Co myślał Apple?
rano
7
Nie wiem, ale ilekroć uważam, że podstawowe dane są dobrym pomysłem, staram się znaleźć prostsze i łatwiejsze w utrzymaniu rozwiązanie.
Nick Weaver
5
Dzięki! To doskonała odpowiedź. Wydaje się to skomplikowane, ale nie jest tak źle, gdy nauczysz się kroków. Największym problemem jest to, że dokumentacja nie określa tego w ten sposób.
bentford
2
Oto zaktualizowany link do dostosowywania procesu migracji. Poruszył się od czasu napisania tego postu. developer.apple.com/library/ios/documentation/Cocoa/Conceptual/…
user1021430
@NickWeaver jak określasz destinationStoreURL? Tworzysz go, czy jest on tworzony przez podstawowy system danych podczas migracji ????
dev gr
3

Te pytania są powiązane:

Problemy z pamięcią podczas migracji dużych baz danych CoreData na iPhone'a

Wieloprzebiegowa migracja danych podstawowych w fragmentach z iOS

Aby zacytować pierwszy link:

Jest to omówione w oficjalnej dokumentacji w sekcji „Wiele kart”, jednak wygląda na to, że ich sugerowanym podejściem jest podzielenie migracji według typu jednostki, tj. Utworzenie wielu modeli mapowania, z których każdy migruje podzbiór typów jednostek kompletny model danych.

occulus
źródło
1
Dzięki za linki. Problem polega na tym, że nikt tak naprawdę nie wyjaśnia szczegółowo, jak ustawić to w wielu przebiegach. Jak skonfigurować wiele modeli mapowania, aby działały skutecznie?
Jason
-5

Załóżmy, że Twój schemat bazy danych zawiera 5 encji, np. Osoba, student, kurs, klasa i rejestracja, aby użyć standardowego przykładu, w którym uczeń tworzy podklasy osoba, klasa implementuje kurs, a rejestracja dołącza do klasy i studenta. Jeśli dokonałeś zmian we wszystkich tych definicjach tabel, musisz zacząć od klas podstawowych i przechodzić w górę. Nie możesz więc zacząć od konwersji rejestracji, ponieważ każdy rekord rejestracji zależy od posiadania tam klasy i uczniów. Tak więc należy rozpocząć od migracji tylko tabeli Person, skopiowania istniejących wierszy do nowej tabeli i wypełnienia wszelkich nowych pól (jeśli to możliwe) i odrzucenia usuniętych kolumn. Wykonuj każdą migrację w ramach puli automatycznego uwalniania, aby po jej zakończeniu pamięć wróciła do pracy.

Po utworzeniu tabeli Person możesz przekonwertować tabelę uczniów. Następnie przejdź do Course, potem Class, a na końcu do tabeli rejestracyjnej.

Inną kwestią jest liczba rekordów, jeśli tak jak osoba ma tysiąc wierszy, musiałbyś co 100 lub więcej wykonywać odpowiednik NSManagedObject wydania, który ma na celu przekazanie kontekstu zarządzanego obiektu [moc refreshObject: ob mergeChanges: NIE]; Ustaw również czasomierz nieaktualnych danych na niski poziom, aby pamięć była często opróżniana.

Tata Smerf
źródło
Czy zasadniczo sugerujesz, aby mieć nowy podstawowy schemat danych, który nie jest częścią starego schematu, i ręcznie skopiować dane do nowego schematu?
Jason
-1 Ręczne mapowanie bazy danych nie jest konieczne. Wdrożone bazy danych można migrować przy użyciu uproszczonej migracji lub jawnych modeli MappingModels.
bentford