thxou

Ama la sabiduría. Desea el conocimiento.

Migración Sencilla De Modelos en Core Data

| Comments

Probablemente los que ya habéis usado Core Data en vuestras aplicaciones, os habréis encontrado con que cada vez que modificas el modelo de datos, al volver a instalar la app en el simulador o dispositivo, la aplicación genera una excepción y se cierra. En el entorno de desarrollo, la solución inmediata es borrar la aplicación del simulador o dispositivo y volverla a instalar, no obstante esto no nos sirve de cara a actualizar nuestra app que ya está subida a la App Store, por razones obvias.

El problema!

Sucede que el sistema de almacenamiento en Core Data solo puede ser abierto por el mismo modelo que se ha usado para crearlo, es por eso que cuando cambias el modelo (añades algún atributo, entidad, etc), el modelo modificado deja de ser igual al modelo que se ha usado para crear el almacenamiento, por lo tanto, son incompatibles y el modelo nuevo resulta no apto para llevar a cabo la tarea.

La solución es llevar a cabo una migración entre versiones del modelo (la anterior y la modificada). Para hacer esta migración, Core Data usa un modelo de Mapeo que le permite saber que cambios tiene que realizar para que el nuevo modelo sea capaz de abrir el almacenamiento como lo hacía el modelo anterior.

Poniéndonos ya en materia, existen 2 tipos de migración: la migración automática, de la que hablaremos ahora, y la migración manual. Estas 2 tan solo difieren en una cosa: El modelo de mapeo usado para hacer la migración. Os paso a explicar más detalladamente el tema.

Migración automática

También se le conoce como migración ligera. Es el camino fácil para realizar la tarea, y consiste en que Core Data es quien provee el modelo de mapeo a usarse en la migración, hace esto intentando deducir los cambios que se han hecho a través de un análisis en los esquemas de los 2 modelos.

Este tipo de migración requiere que el modelo modificado tan solo haya sufrido sencillos cambios en su estructura. Ahora, que entiende Core Data como “sencillos cambios”?. Pues los siguientes:

  • Añadir o quitar un atributo.
  • Cambiar la propiedad optional de los atributos.
  • Asignar un valor por defecto a un atributo.
  • Renombrar entidades o atributos usando el campo Renaming ID.

Para casos más complejos es necesario usar la migración manual, en ese caso te toca a ti proveer el mapeo para la migración, proceso que se complica un poco, así que lo dejaremos para otra entrada.

Añadiendo un nuevo modelo

Vamos con una aplicación práctica para ver mejor como va el tema. Para esto, he creado un proyecto que usa Core Data con un modelo muy sencillo ya definido y que puedes descargar aquí.

Ya que para hacer una migración son imprescindibles 2 versiones de un modelo, vamos a crear otro diferente a la que ya tenemos en el proyecto yendo al menú Editor > Add Model Version. Dejemos el nombre por defecto por esta vez y luego clic en Finish. Esto nos creará Notes 2.xcdatamodel y una especie de carpeta contenedora llamada Notes.xcdatamodeld, en la cual también verás incluido nuestro modelo por defecto.

Core Data Migration en ThXou

Si nos fijamos, uno de los modelos aparece con un check de color verde. Esto quiere decir que es ese el modelo que estamos usando actualmente. Como queremos usar el nuevo modelo a partir de ahora, seleccionamos la carpeta contenedora Notes.xcdatamodeld y en panel File Inspector de la derecha, en el apartado Model Version, cambiamos la opción Current a Notes 2, que es nuestro nuevo modelo.

Vamos a hacer un par de modificaciones a Notes 2. Selecciona la entidad Note y añade un nuevo atributo llamado descriptionText de tipo String. Ahora vamos a renombrar el atributo backgroundColor a solo background.

Core Data Migration in ThXou

Vamos a sanear cualquier error que pueda ocurrir después del cambio en el modelo de nuestra aplicación haciendo la migración, pero antes comentarte que al renombrar entidades o atributos es necesario definir el campo Renaming ID. Selecciona el atributo que hemos renombrado: background, y en el panel Data Model Inspector de la derecha, escribe en el campo Renaming ID, el nombre anterior del atributo, osea backgroundColor (Si no te acuerdas puedes mirar en la primera versión del modelo). Esto es obligatorio para cuando vayas a renombrar entidades o atributos.

Core Data Migration

Haciendo la mudanza

Lo que nos queda ahora es decirle a Core Data que haga la migración automática al iniciar la aplicación. Para esto nos tenemos que dirigir al Core Data Stack localizado en el fichero AppDelegate.m. En el vas a encontrar el método getter del Persistent Store Coordinator. Modifícalo con el siguiente código:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
    if (_persistentStoreCoordinator != nil) {
        return _persistentStoreCoordinator;
    }

    NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"Notes.sqlite"];

    NSError *error = nil;
    _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];

    // (2)
    NSDictionary *options = @{NSMigratePersistentStoresAutomaticallyOption:@YES, NSInferMappingModelAutomaticallyOption:@YES};

    if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error]) { // (1)
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }

    return _persistentStoreCoordinator;
}

Dentro hay que modificar el método addPersistentStoreWithType:configuration:URL:options:error:(1), que es quien crea el almacenamiento para la app, en concreto el parámetro options:, a quien por defecto se le pasa nil, pero nosotros le asignamos el diccionario options con las keys que van a decirle a Core Data que lleve a cabo la migración automática (2).

Hasta este punto, ya puedes poner a correr la aplicación que estés migrando, verás que todo marcha sobre ruedas. Si no te salta ningún error ni ocurre ningún problema es porque la migración se ha realizado satisfactoriamente.

Como se si mi app puede migrar automáticamente?

Esto es un extra, por si se te plantea la pregunta para tus proyectos. Hemos visto que hay 2 formas de hacer la migración: automática (Fácil) y manual (difícil), como saber si mi app puede migrar automáticamente?. La respuesta está en preguntarle a la clase NSMappingModel si es capaz o no de crear el modelo de mapeo por si mismo. Esto lo hacemos con el método inferredMappingModelForSourceModel:destinationModel:error::

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- (BOOL)miModeloPuedeMigrar
{
    NSURL *modeloAntiguoURL = [[NSBundle mainBundle] URLForResource:@"Notes" withExtension:@"momd"];
    NSManagedObjectModel *modeloAntiguo = [[NSManagedObjectModel alloc] initWithContentsOfURL:modeloAntiguoURL];

    NSURL *modeloNuevoURL = [[NSBundle mainBundle] URLForResource:@"Notes 2" withExtension:@"momd"];
    NSManagedObjectModel *modeloNuevo = [[NSManagedObjectModel alloc] initWithContentsOfURL:modeloNuevoURL];

    NSMappingModel *modeloDeMapeo =
        [NSMappingModel inferredMappingModelForSourceModel:modeloAntiguo
                        destinationModel:modeloNuevo error:error];

    // si Core Data es capaz de crear el modelo entonces 
    // retornamos YES de lo contrario NO
    if (!modeloDeMapeo)
    {
        return NO;
    }
    return YES;
}

Este método lo puedes usar en el método application:didFinishLaunchingWithOptions:launchOptions del AppDelegate.m, con un NSLog que te devuelva SI o NO dependiendo del valor de retorno del método miModeloPuedeMigrar.

Conclución

Este tutorial, como dije antes, es para cuando tienes que hacer ciertos cambios de los listados arriba. Si lo tuyo requiere algo diferente te va a tocar optar por aprender a realizar un mapeo personalizado.

Ten en cuenta cuando renombres entidades, que las clases modelo asociadas no se cambian, por lo que es algo de lo que te tienes que ocupar tu manualmente o usando la herramienta de refactorización de Xcode.

Comments