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
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í:
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 double
y 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
2. Elija un nombre, elegí StepOne
3. Establecer el modelo de datos de origen y destino
Modelo de mapeo, paso uno
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
.
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
{
NSManagedObject *newObject =
[NSEntityDescription insertNewObjectForEntityForName:[mapping destinationEntityName]
inManagedObjectContext:[manager destinationContext]];
NSDate *date = [sInstance valueForKey:@"timeStamp"];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setTimeStyle:NSDateFormatterMediumStyle];
[dateFormatter setDateStyle:NSDateFormatterMediumStyle];
[newObject setValue:[dateFormatter stringFromDate:date] forKey:@"printedDate"];
[dateFormatter release];
[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 NSMappingModel
clase 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 cdm
en 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.