Esempio o spiegazione della migrazione dei dati di base con più passaggi?


85

La mia app per iPhone deve migrare il suo archivio dati principale e alcuni database sono piuttosto grandi. La documentazione di Apple suggerisce di utilizzare "più passaggi" per migrare i dati e ridurre l'utilizzo della memoria. Tuttavia, la documentazione è molto limitata e non spiega molto bene come farlo effettivamente. Qualcuno può indicarmi un buon esempio o spiegare in dettaglio il processo su come farlo effettivamente?


hai avuto problemi di memoria effettivamente? La tua migrazione è leggera o desideri utilizzare NSMigrationManager?
Nick Weaver

Sì, la console GDB ha mostrato che c'erano avvisi di memoria e quindi l'app si arresta in modo anomalo a causa della memoria limitata. Ho provato sia la migrazione leggera che NSMigrationManager, ma in questo momento sto cercando di utilizzare NSMigrationManager.
Jason

ok, puoi entrare un po 'più nel dettaglio cosa è cambiato?
Nick Weaver

finalmente l'ho scoperto, ho letto la mia risposta.
Nick Weaver

Ciao Jason, potresti risolvere il problema nella domanda?
Yuchen Zhong

Risposte:


174

Ho capito cosa suggerisce Apple nella loro documentazione . In realtà è molto facile, ma c'è ancora molta strada da fare prima che sia ovvio. Illustrerò la spiegazione con un esempio. La situazione iniziale è questa:

Data Model versione 1

inserisci qui la descrizione dell'immagine inserisci qui la descrizione dell'immagine

È il modello che ottieni quando crei un progetto con il modello "app basata sulla navigazione con archiviazione dei dati di base". L'ho compilato e ho fatto un po 'di duro lavoro con l'aiuto di un ciclo for per creare circa 2k voci tutte con valori diversi. Ecco 2.000 eventi con un valore NSDate.

Ora aggiungiamo una seconda versione del modello di dati, che assomiglia a questo:

inserisci qui la descrizione dell'immagine

Data Model versione 2

La differenza è: l'entità Evento è scomparsa e ne abbiamo due nuove. Uno che memorizza un timestamp come a doublee il secondo che dovrebbe memorizzare una data come NSString.

L'obiettivo è trasferire tutta la versione 1 eventi della alle due nuove entità e convertire i valori durante la migrazione. Ciò si traduce in due volte i valori ciascuno come un tipo diverso in un'entità separata.

Per migrare, scegliamo la migrazione manualmente e questo lo facciamo con i modelli di mappatura. Questa è anche la prima parte della risposta alla tua domanda. Faremo la migrazione in due passaggi, perché la migrazione di 2k voci richiede molto tempo e ci piace mantenere basso il footprint di memoria.

Potresti anche andare avanti e dividere ulteriormente questi modelli di mappatura per migrare solo gli intervalli delle entità. Diciamo che abbiamo un milione di record, questo potrebbe mandare in crash l'intero processo. È possibile restringere le entità recuperate con un predicato di filtro .

Torniamo ai nostri due modelli di mappatura.

Creiamo il primo modello di mappatura in questo modo:

1. Nuovo file -> Risorsa -> Modello di mappatura inserisci qui la descrizione dell'immagine

2. Scegli un nome, ho scelto StepOne

3. Impostare il modello di dati di origine e di destinazione

inserisci qui la descrizione dell'immagine

Fase uno del modello di mappatura

inserisci qui la descrizione dell'immagine

inserisci qui la descrizione dell'immagine

inserisci qui la descrizione dell'immagine

La migrazione a più passaggi non necessita di criteri di migrazione delle entità personalizzate, tuttavia lo faremo per ottenere un po 'più di dettagli per questo esempio. Quindi aggiungiamo una policy personalizzata all'entità. Questa è sempre una sottoclasse di NSEntityMigrationPolicy.

inserisci qui la descrizione dell'immagine

Questa classe di criteri implementa alcuni metodi per far sì che la nostra migrazione avvenga. Tuttavia è semplice in questo caso in modo dovremo implementare un solo metodo: createDestinationInstancesForSourceInstance:entityMapping:manager:error:.

L'implementazione sarà simile a questa:

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;
}

Passaggio finale: la migrazione stessa

Salterò la parte per impostare il secondo modello di mappatura che è quasi identico, solo un timeIntervalSince1970 utilizzato per convertire NSDate in un doppio.

Infine dobbiamo attivare la migrazione. Salterò il codice boilerplate per ora. Se ne hai bisogno, posterò qui. Può essere trovato su Personalizzazione del processo di migrazione , è solo una fusione dei primi due esempi di codice. La terza e ultima parte verrà modificata come segue: Invece di usare il metodo class della NSMappingModelclasse mappingModelFromBundles:forSourceModel:destinationModel:useremo ilinitWithContentsOfURL: perché il metodo class restituirà solo uno, forse il primo, modello di mappatura trovato nel bundle.

Ora abbiamo i due modelli di mappatura che possono essere utilizzati in ogni passaggio del ciclo e inviare il metodo di migrazione al gestore della migrazione. Questo è tutto.

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];
} 

Appunti

  • Un modello di mappatura termina nel cdmbundle.

  • L'archivio di destinazione deve essere fornito e non dovrebbe essere l'archivio di origine. Dopo la corretta migrazione, è possibile eliminare il vecchio e rinominare quello nuovo.

  • Ho apportato alcune modifiche al modello dati dopo la creazione dei modelli di mappatura, questo ha comportato alcuni errori di compatibilità, che ho potuto risolvere solo ricreando i modelli di mappatura.


59
Maledizione, è complicato. Cosa stava pensando Apple?
aroth

7
Non lo so, ma ogni volta che penso che i dati di base siano una buona idea, mi sforzo di trovare una soluzione più semplice e più gestibile.
Nick Weaver

5
Grazie! Questa è una risposta eccellente. Sembra complicato, ma non è poi così male una volta imparati i passaggi. Il problema più grande è che la documentazione non lo spiega in questo modo.
Bentford

2
Ecco il collegamento aggiornato alla personalizzazione del processo di migrazione. È stato spostato da quando è stato scritto questo post. developer.apple.com/library/ios/documentation/Cocoa/Conceptual/…
user1021430

@ NickWeaver come stai determinando destinationStoreURL? Lo stai creando o viene creato dal sistema di dati di base durante il processo di migrazione ????
dev gr

3

Queste domande sono correlate:

Problemi di memoria durante la migrazione di datastore CoreData di grandi dimensioni su iPhone

Migrazione dei dati core a più passaggi in blocchi con iOS

Per citare il primo collegamento:

Questo è discusso nella documentazione ufficiale nella sezione "Passaggi multipli", tuttavia sembra che l'approccio suggerito sia quello di suddividere la migrazione per tipo di entità, ovvero creare più modelli di mappatura, ciascuno dei quali migra un sottoinsieme dei tipi di entità dal modello dati completo.


1
Grazie per i link. Il problema è che nessuno spiega in dettaglio come configurarlo in più passaggi. Come devo impostare più modelli di mappatura in modo che funzionino in modo efficace?
Jason

-5

Supponiamo che lo schema del tuo database abbia 5 entità, ad esempio persona, studente, corso, classe e registrazione per utilizzare il tipo standard di esempio, dove studente sottoclassi persona, classe implementa il corso e la registrazione si unisce a classe e studente. Se hai apportato modifiche a tutte queste definizioni di tabella, devi iniziare dalle classi di base e procedere verso l'alto. Quindi, non puoi iniziare con la conversione delle registrazioni, perché ogni record di registrazione dipende dalla presenza di classe e studenti. Quindi, inizieresti con la migrazione solo della tabella Person, copiando le righe esistenti nella nuova tabella e riempiendo i nuovi campi (se possibile) e scartando le colonne rimosse. Esegui ogni migrazione all'interno di un pool di rilascio automatico, in modo che, una volta completata, la tua memoria torni ad iniziare.

Una volta terminata la tabella Persona, è possibile convertire la tabella Studente. Quindi salta su Corso e poi su Classe e infine sulla tabella di registrazione.

L'altra considerazione è il numero di record, se come Person avesse mille righe, dovresti, ogni 100 circa, eseguire l'equivalente NSManagedObject di una versione, che è per dire al contesto dell'oggetto gestito [moc refreshObject: ob mergeChanges: NO]; Imposta anche il timer dei dati obsoleti su un livello basso, in modo che la memoria venga svuotata spesso.


Quindi stai essenzialmente suggerendo di avere un nuovo schema di dati di base che non fa parte del vecchio schema e di copiare i dati nel nuovo schema a mano?
Jason

-1 La mappatura manuale del database non è necessaria. È possibile migrare i database distribuiti utilizzando una migrazione leggera o con MappingModels espliciti.
Bentford
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.