¿Ejemplo o explicación de la migración de datos centrales con varias pasadas?

85

Mi aplicación de iPhone necesita migrar su almacén de datos principal y algunas de las bases de datos son bastante grandes. La documentación de Apple sugiere usar "múltiples pases" para migrar datos para reducir el uso de memoria. Sin embargo, la documentación es muy limitada y no explica muy bien cómo hacer esto. ¿Alguien puede señalarme un buen ejemplo o explicar en detalle el proceso de cómo llevarlo a cabo?

Jason
fuente
en realidad, ¿corriste en problemas de memoria? ¿Su migración es liviana o desea utilizar un NSMigrationManager?
Nick Weaver
Sí, la consola de GDB mostró que había advertencias de memoria y luego la aplicación se bloquea debido a la memoria limitada. Probé tanto la migración ligera como NSMigrationManager, pero ahora estoy tratando de usar NSMigrationManager.
Jason
ok, ¿puedes detallar un poco más lo que ha cambiado?
Nick Weaver
finalmente, lo he descubierto, lee mi respuesta.
Nick Weaver
Hola Jason, ¿podrías arreglar el me gusta en la pregunta?
Yuchen Zhong

Respuestas:

174

He descubierto lo que Apple sugiere en su documentación . En realidad, es muy fácil, pero queda un largo camino por recorrer antes de que sea obvio. Ilustraré la explicación con un ejemplo. La situación inicial es esta:

Modelo de datos versión 1

ingrese la descripción de la imagen aquí ingrese la descripción de la imagen aquí

Es el modelo que obtiene cuando crea un proyecto con la plantilla "aplicación basada en navegación con almacenamiento de datos centrales". Lo compilé e hice algunos golpes con la ayuda de un bucle for para crear alrededor de 2k entradas, todas con algunos valores diferentes. Ahí vamos 2.000 eventos con un valor NSDate.

Ahora agregamos una segunda versión del modelo de datos, que se ve así:

ingrese la descripción de la imagen aquí

Modelo de datos versión 2

La diferencia es: la entidad Event se ha ido y tenemos dos nuevas. Uno que almacena una marca de tiempo como doubley el segundo que debería almacenar una fecha como NSString.

El objetivo es transferir todos los eventos de la versión 1 a las dos nuevas entidades y convertir los valores a lo largo de la migración. Esto da como resultado el doble de valores, cada uno como un tipo diferente en una entidad separada.

Para migrar, elegimos la migración a mano y esto lo hacemos con modelos de mapeo. Esta es también la primera parte de la respuesta a su pregunta. Haremos la migración en dos pasos, porque la migración de 2k entradas lleva mucho tiempo y nos gusta mantener baja la huella de memoria.

Incluso podría seguir adelante y dividir estos modelos de mapeo aún más para migrar solo rangos de las entidades. Digamos que tenemos un millón de registros, esto puede bloquear todo el proceso. Es posible reducir las entidades buscadas con un predicado de filtro .

Volvamos a nuestros dos modelos de mapeo.

Creamos el primer modelo de mapeo así:

1. Nuevo archivo -> Recurso -> Modelo de mapeo ingrese la descripción de la imagen aquí

2. Elija un nombre, elegí StepOne

3. Establecer el modelo de datos de origen y destino

ingrese la descripción de la imagen aquí

Modelo de mapeo, paso uno

ingrese la descripción de la imagen aquí

ingrese la descripción de la imagen aquí

ingrese la descripción de la imagen aquí

La migración de múltiples pases no necesita políticas de migración de entidades personalizadas, sin embargo, lo haremos para obtener un poco más de detalles para este ejemplo. Entonces agregamos una política personalizada a la entidad. Esta es siempre una subclase de NSEntityMigrationPolicy.

ingrese la descripción de la imagen aquí

Esta clase de política implementa algunos métodos para que suceda nuestra migración. Sin embargo, es simple en este caso por lo que tendremos que aplicar un solo método: createDestinationInstancesForSourceInstance:entityMapping:manager:error:.

La implementación se verá así:

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

Paso final: la migración en sí

Saltaré la parte para configurar el segundo modelo de mapeo que es casi idéntico, solo un timeIntervalSince1970 usado para convertir el NSDate en un doble.

Finalmente, necesitamos activar la migración. Saltaré el código repetitivo por ahora. Si lo necesita, lo publicaré aquí. Se puede encontrar en Personalización del proceso de migración , es solo una combinación de los dos primeros ejemplos de código. La tercera y última parte se modificará de la siguiente manera: en lugar de usar el método de clase de la NSMappingModelclase mappingModelFromBundles:forSourceModel:destinationModel:, usaremos el initWithContentsOfURL:porque el método de clase devolverá solo uno, tal vez el primero, modelo de mapeo encontrado en el paquete.

Ahora tenemos los dos modelos de mapeo que se pueden usar en cada paso del ciclo y enviar el método de migración al administrador de migración. Eso es.

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

Notas

  • Un modelo de mapeo termina cdmen el paquete.

  • Se debe proporcionar la tienda de destino y no debe ser la tienda de origen. Después de una migración exitosa, puede eliminar el antiguo y cambiar el nombre del nuevo.

  • Hice algunos cambios en el modelo de datos después de la creación de los modelos de mapeo, esto resultó en algunos errores de compatibilidad, que solo pude resolver recreando los modelos de mapeo.

Nick Weaver
fuente
59
Maldito infierno que es complicado. ¿Qué estaba pensando Apple?
2012
7
No lo sé, pero siempre que creo que los datos centrales son una buena idea, me esfuerzo por encontrar una solución más simple y fácil de mantener.
Nick Weaver
5
¡Gracias! Esta es una excelente respuesta. Parece complicado, pero no es tan malo una vez que aprendes los pasos. El mayor problema es que la documentación no te lo explica así.
bentford
2
Aquí está el enlace actualizado para personalizar el proceso de migración. Se ha movido desde que se escribió esta publicación. developer.apple.com/library/ios/documentation/Cocoa/Conceptual/…
user1021430
@NickWeaver ¿cómo se determina destinationStoreURL? ¿Lo está creando o lo crea el sistema de datos central durante el proceso de migración?
dev gr
3

Estas preguntas están relacionadas:

Problemas de memoria al migrar grandes almacenes de datos CoreData en iPhone

Migración de datos principales de múltiples pases en fragmentos con iOS

Para citar el primer enlace:

Esto se discute en la documentación oficial en la sección "Múltiples pases", sin embargo, parece que su enfoque sugerido es dividir su migración por tipo de entidad, es decir, hacer múltiples modelos de mapeo, cada uno de los cuales migra un subconjunto de los tipos de entidad de la modelo de datos completo.

occulus
fuente
1
Gracias por los enlaces. El problema es que nadie explica en detalle cómo configurarlo en varias pasadas. ¿Cómo debo configurar varios modelos de mapeo para que funcionen de manera efectiva?
Jason
-5

Suponga que el esquema de su base de datos tiene 5 entidades, por ejemplo, persona, estudiante, curso, clase y registro para usar el tipo estándar de ejemplo, donde el estudiante subclasifica persona, la clase implementa el curso y el registro se une a la clase y al estudiante. Si ha realizado cambios en todas estas definiciones de tabla, debe comenzar en las clases base y avanzar. Por lo tanto, no puede comenzar con la conversión de registros, porque cada registro de registro depende de que haya clase y estudiantes allí. Por lo tanto, comenzaría migrando solo la tabla Person, copiando las filas existentes en la nueva tabla y completando los campos nuevos que estén allí (si es posible) y descartando las columnas eliminadas. Realice cada migración dentro de un grupo de liberación automática, de modo que una vez que haya terminado, su memoria vuelva a comenzar.

Una vez que la tabla de personas está lista, puede convertir la tabla de estudiantes. Luego salte a Curso y luego Clase, y finalmente a la tabla de Registro.

La otra consideración es el número de registros, si como Person tuviera mil filas, tendría que, cada 100 aproximadamente, ejecutar el NSManagedObject equivalente de una versión, que es decirle al contexto del objeto administrado [moc refreshObject: ob mergeChanges: NO]; También configure su temporizador de datos obsoletos muy bajo, para que la memoria se vacíe con frecuencia.

Papá Pitufo
fuente
Entonces, ¿esencialmente está sugiriendo tener un nuevo esquema de datos centrales que no sea parte del esquema anterior y copiar los datos en el nuevo esquema a mano?
Jason
-1 No es necesario mapear manualmente su base de datos. Puede migrar bases de datos implementadas mediante una migración ligera o con MappingModels explícitos.
bentford