Ir a la consola

Migra tu app de Parse para iOS a Firebase

Si eres un usuario de Parse que busca un back-end alternativo como solución de servicio, Firebase puede ser la opción ideal para tu app de iOS.

Esta guía describe cómo integrar servicios específicos con tu app. Si deseas ver las instrucciones básicas para la configuración de Firebase, consulta la guía Configuración de iOS.

Google Analytics para Firebase

Google Analytics para Firebase es una solución de medición de apps gratuita que proporciona estadísticas sobre el uso de la app y la participación de los usuarios. Analytics se integra con distintas funciones de Firebase y te proporciona una capacidad ilimitada de generar informes sobre un total de hasta 500 eventos distintos que puedes definir con el SDK de Firebase.

Consulta la documentación de Google Analytics para Firebase a fin de obtener más información.

Estrategia de migración sugerida

Usar varios proveedores de herramientas de análisis es una situación común que se aplica con facilidad a Google Analytics para Firebase. Solo agrégalo a tu app para aprovechar los eventos y las propiedades del usuario que Analytics recopila automáticamente, como la primera apertura, la actualización de la app, el modelo de dispositivo y la edad.

En el caso de las propiedades de usuario y los eventos personalizados, puedes emplear una estrategia de escritura doble, tanto con Parse Analytics como con Google Analytics para Firebase, para registrar eventos y propiedades, lo que te permite implementar la solución nueva de manera gradual.

Comparación de código

Parse Analytics

// Start collecting data
[PFAnalytics trackAppOpenedWithLaunchOptions:launchOptions];

NSDictionary *dimensions = @{
  // Define ranges to bucket data points into meaningful segments
  @"priceRange": @"1000-1500",
  // Did the user filter the query?
  @"source": @"craigslist",
  // Do searches happen more often on weekdays or weekends?
  @"dayType": @"weekday"
};
// Send the dimensions to Parse along with the 'search' event
[PFAnalytics trackEvent:@"search" dimensions:dimensions];

Google Analytics para Firebase

// Obtain the AppMeasurement instance and start collecting data
[FIRApp configure];

// Send the event with your params
[FIRAnalytics logEventWithName:@"search" parameters:@{
  // Define ranges to bucket data points into meaningful segments
  @"priceRange": @"1000-1500",
  // Did the user filter the query?
  @"source": @"craigslist",
  // Do searches happen more often on weekdays or weekends?
  @"dayType": @"weekday"
}];

Firebase Realtime Database

Firebase Realtime Database es una base de datos NoSQL alojada en la nube. Los datos se almacenan en formato JSON y se sincronizan en tiempo real con cada cliente conectado.

Consulta los documentos de Firebase Realtime Database para obtener más información.

Diferencias con los datos de Parse

Objetos

En Parse, almacenas un PFObject o una subclase de este que contiene pares clave-valor de datos compatibles con el formato JSON. Los datos no tienen esquema, por lo que no necesitas especificar qué claves existen en cada PFObject.

Todos los datos de Firebase Realtime Database se almacenan como objetos JSON y no hay equivalente para PFObject; simplemente, escribes a los tipos de valores del árbol de JSON que corresponden a los tipos disponibles de JSON.

El siguiente es un ejemplo de cómo podrías guardar los puntajes máximos para un juego.

Parse
PFObject *gameScore = [PFObject objectWithClassName:@"GameScore"];
gameScore[@"score"] = @1337;
gameScore[@"playerName"] = @"Sean Plott";
gameScore[@"cheatMode"] = @NO;
[gameScore saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
  if (succeeded) {
    // The object has been saved.
  } else {
    // There was a problem, check error.description
  }
}];
Firebase
// Create a reference to the database
FIRDatabaseReference *ref = [[FIRDatabase database] reference];
NSString *key = [[ref child:@"scores"] childByAutoId].key;
NSDictionary *score = @{@"score": @1337,
                        @"playerName": @"Sean Plott",
                        @"cheatMode": @NO};
[key setValue:score withCompletionBlock:^(NSError *error,  FIRDatabaseReference *ref) {
  if (error) {
    // The object has been saved.
  } else {
    // There was a problem, check error.description
  }
}];
Para obtener más detalles, consulta la guía Lectura y escritura de datos en iOS.

Relaciones entre datos

Un PFObject puede tener una relación con otro PFObject: cualquier objeto puede usar otros objetos como valores.

En Firebase Realtime Database, las relaciones se expresan mejor a través de estructuras compactas de datos que dividen los datos en rutas de acceso diferentes, de modo que se pueden descargar de forma eficiente en llamadas separadas.

El siguiente es un ejemplo de cómo podrías estructurar la relación entre las publicaciones y sus autores en una app de blog.

Parse
// Create the author
PFObject *myAuthor = [PFObject objectWithClassName:@"Author"];
myAuthor[@"name"] = @"Grace Hopper";
myAuthor[@"birthDate"] = @"December 9, 1906";
myAuthor[@"nickname"] = @"Amazing Grace";

// Create the post
PFObject *myPost = [PFObject objectWithClassName:@"Post"];
myPost[@"title"] = @"Announcing COBOL, a New Programming Language";

// Add a relation between the Post and the Author
myPost[@"parent"] = myAuthor;

// This will save both myAuthor and myPost
[myPost saveInBackground];
Firebase
// Create a reference to the database
FIRDatabaseReference *ref = [[FIRDatabase database] reference];

// Create the author
NSString *myAuthorKey = @"ghopper";
NSDictionary *author = @{@"name": @"Grace Hopper",
                         @"birthDate": @"December 9, 1906",
                         @"nickname": @"Amazing Grace"};
// Save the author
[[ref child:myAuthorKey] setValue:author]

// Create and save the post
NSString *key = [[ref child:@"posts"] childByAutoId].key;
NSDictionary *post = @{@"author": myAuthorKey,
                       @"title": @"Announcing COBOL, a New Programming Language"};
[key setValue:post]

El siguiente diseño de datos es el resultado.

{
  // Info about the authors
  "authors": {
    "ghopper": {
      "name": "Grace Hopper",
      "date_of_birth": "December 9, 1906",
      "nickname": "Amazing Grace"
    },
    ...
  },
  // Info about the posts: the "author" fields contains the key for the author
  "posts": {
    "-JRHTHaIs-jNPLXOQivY": {
      "author": "ghopper",
      "title": "Announcing COBOL, a New Programming Language"
    }
    ...
  }
}
Para ver más detalles, consulta la guía Cómo estructurar tu base de datos.

Lectura de datos

En Parse, los datos se leen mediante el ID de un objeto Parse específico o a través de consultas con PFQuery.

Para recuperar datos en Firebase, debe adjuntar un agente de escucha asíncrono a la referencia de base de datos. El agente de escucha se activa una vez para el estado inicial de los datos y nuevamente cuando se cambian los datos, de modo que no necesitarás agregar código para determinar si los datos cambiaron.

El siguiente ejemplo muestra cómo podrías recuperar las puntuaciones de un jugador en particular, de acuerdo con el ejemplo que se presentó en la sección "Objetos".

Parse
PFQuery *query = [PFQuery queryWithClassName:@"GameScore"];
[query whereKey:@"playerName" equalTo:@"Dan Stemkoski"];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
  if (!error) {
    for (PFObject *score in objects) {
      NSString *gameScore = score[@"score"];
      NSLog(@"Retrieved: %@", gameScore);
    }
  } else {
    // Log details of the failure
    NSLog(@"Error: %@ %@", error, [error userInfo]);
  }
}];
Firebase
// Create a reference to the database
FIRDatabaseReference *ref = [[FIRDatabase database] reference];

// This type of listener is not one time, and you need to cancel it to stop
// receiving updates.
[[[[ref child:@"scores"] queryOrderedByChild:@"playerName"] queryEqualToValue:@"Dan Stemkoski"]
    observeEventType:FIRDataEventTypeChildAdded withBlock:^(FIRDataSnapshot *snapshot) {
  // This will fire for each matching child node.
  NSDictionary *score = snapshot.value;
  NSString gameScore = score[@"score"];
  NSLog(@"Retrieved: %@", gameScore);
}];
Para conocer más detalles sobre los tipos disponibles de agentes de escucha de eventos y sobre cómo ordenar y filtrar datos, consulta la guía Lectura y escritura de datos en iOS.

Estrategia de migración sugerida

Reconsidera tus datos

Firebase Realtime Database está optimizado para sincronizar datos entre todos los clientes conectados en milisegundos, y la estructura de datos resultante es diferente de los datos principales de Parse. Esto significa que el primer paso de tu migración es considerar qué cambios necesitan tus datos, incluido lo siguiente:

  • Las relaciones de asignación entre tus objetos de Parse y los datos de Firebase.
  • Si tienes relaciones entre elementos superiores y secundarios, cómo dividir tus datos en rutas de acceso diferentes para que se puedan descargar de forma eficiente en llamadas separadas.

Migra tus datos

Una vez que hayas decidido cómo estructurar tus datos en Firebase, necesitas planificar cómo administrar el período durante el cual tu app deberá escribir en ambas bases de datos. Tus opciones son las siguientes:

Sincronización en segundo plano

En esta situación, tienes dos versiones de la app: la versión antigua que usa Parse y una versión nueva que usa Firebase. Las sincronizaciones entre las dos bases de datos se administran por medio de Parse Cloud Code (de Parse a Firebase), y tu código escucha los cambios en Firebase y sincroniza esos cambios con Parse. Antes de comenzar a usar la versión nueva, debes hacer lo siguiente:

  • Convertir los datos de Parse existentes a la estructura nueva de Firebase y escribirlos en Firebase Realtime Database.
  • Escribir funciones de Parse Cloud Code que usen la API de REST de Firebase para escribir en Firebase Realtime Database los cambios que hagan los clientes antiguos en los datos de Parse.
  • Escribir e implementar código que escuche los cambios en Firebase y los sincronice con la base de datos de Parse.

Esta situación garantiza que exista una separación clara entre el código anterior y el nuevo y evita complicaciones en los clientes. Los desafíos de esta situación son administrar grandes conjuntos de datos en la exportación inicial y garantizar que la sincronización bidireccional no genere una recursividad infinita.

Escritura doble

En esta situación, escribes una versión nueva de la app que use tanto Firebase como Parse, y usas Parse Cloud Code para sincronizar los cambios que hagan los clientes antiguos desde los datos de Parse a Firebase Realtime Database. Cuando suficientes usuarios se migren desde la versión solo para Parse de la app, puedes quitar el código de Parse de la versión de escritura doble.

En esta situación, no es necesario agregar código en el servidor. Las desventajas son que no se migran los datos que nadie consulta y que el tamaño de tu app aumenta, ya que debes usar dos SDK.

Firebase Authentication

Firebase Authentication puede autenticar usuarios con contraseñas y proveedores de identidades federadas populares, como Google, Facebook y Twitter. También proporciona bibliotecas de IU para que no tengas que hacer una inversión significativa en implementar y mantener una experiencia plena de autenticación para tu app en todas las plataformas.

Consulta los documentos de Firebase Authentication para obtener más información.

Diferencias con la autenticación de Parse

Parse proporciona una clase de usuario especializada llamada PFUser que administra automáticamente la funcionalidad necesaria para la administración de cuentas de usuario. PFUser es una subclase de PFObject, lo que significa que los datos de usuario están disponibles en los datos de Parse y se pueden extender con campos adicionales, al igual que cualquier otro PFObject.

Un FIRUser tiene un conjunto fijo de propiedades básicas (un ID único, una dirección de correo electrónico principal, un nombre y una URL de foto) que se almacenan en la base de datos de usuarios de un proyecto independiente. El usuario puede actualizar esas propiedades. No puedes agregar otras propiedades al objeto FIRUser directamente; en lugar de ello, puedes almacenar las propiedades adicionales en Firebase Realtime Database.

El siguiente ejemplo muestra cómo podrías registrar un usuario y agregar un campo adicional para el número de teléfono.

Parse
PFUser *user = [PFUser user];
user.username = @"my name";
user.password = @"my pass";
user.email = @"email@example.com";

// other fields can be set just like with PFObject
user[@"phone"] = @"415-392-0202";

[user signUpInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
  if (!error) {
    // Hooray! Let them use the app now.
  } else {
    // Something went wrong
    NSString *errorString = [error userInfo][@"error"];
  }
}];
Firebase
[[FIRAuth auth] createUserWithEmail:@"email@example.com"
                           password:@"my pass"
                         completion:^(FIRUser *_Nullable user, NSError *_Nullable error) {
  if (!error) {
    FIRDatabaseReference *ref = [[FIRDatabase database] reference];
    [[[[ref child:@"users"] child:user.uid] child:@"phone"] setValue:@"415-392-0202"
  } else {
    // Something went wrong
    NSString *errorString = [error userInfo][@"error"];
  }
}];

Estrategia de migración sugerida

Migra las cuentas

Para migrar cuentas de usuario de Parse a Firebase, exporta tu base de datos de usuarios a un archivo JSON o CSV y, luego, impórtalo al proyecto de Firebase con el comando auth:import de Firebase CLI.

Primero, exporta tu base de datos de usuarios desde la consola de Parse o tu base de datos local. Por ejemplo, un archivo JSON exportado desde la consola de Parse se podría ser similar al siguiente:

{ // Username/password user
  "bcryptPassword": "$2a$10$OBp2hxB7TaYZgKyTiY48luawlTuYAU6BqzxJfpHoJMdZmjaF4HFh6",
  "email": "user@example.com",
  "username": "testuser",
  "objectId": "abcde1234",
  ...
},
{ // Facebook user
  "authData": {
    "facebook": {
      "access_token": "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
      "expiration_date": "2017-01-02T03:04:05.006Z",
      "id": "1000000000"
    }
  },
  "username": "wXyZ987654321StUv",
  "objectId": "fghij5678",
  ...
}

Luego, transforma el archivo exportado al formato necesario para Firebase CLI. Usa el objectId de tus usuarios de Parse como localId de tus usuarios de Firebase. Además, codifica los valores bcryptPassword de Parse en Base 64 y úsalos en el campo passwordHash. Por ejemplo:

{
  "users": [
    {
      "localId": "abcde1234",  // Parse objectId
      "email": "user@example.com",
      "displayName": "testuser",
      "passwordHash": "JDJhJDEwJE9CcDJoeEI3VGFZWmdLeVRpWTQ4bHVhd2xUdVlBVTZCcXp4SmZwSG9KTWRabWphRjRIRmg2",
    },
    {
      "localId": "fghij5678",  // Parse objectId
      "displayName": "wXyZ987654321StUv",
      "providerUserInfo": [
        {
          "providerId": "facebook.com",
          "rawId": "1000000000",  // Facebook ID
        }
      ]
    }
  ]
}

Por último, importa el archivo transformado con la CLI de Firebase y selecciona bcrypt como el algoritmo de hash.

firebase auth:import account_file.json --hash-algo=BCRYPT

Migra los datos de los usuarios

Si almacenas datos adicionales sobre los usuarios, puedes migrarlos a Firebase Realtime Database con las estrategias que se describen en la sección migración de datos. Si migras cuentas mediante el flujo que se describe en la sección migración de cuentas, tus cuentas de Firebase tendrán los mismos ID que tus cuentas de Parse, lo cual te permite migrar y reproducir fácilmente cualquier relación que use el ID de usuario como clave.

Firebase Cloud Messaging

Firebase Cloud Messaging (FCM) es una solución de mensajería multiplataforma que te permite enviar mensajes y notificaciones de forma segura y gratuita. El compositor de Notifications es un servicio gratuito integrado en Firebase Cloud Messaging que permite que los programadores de apps para dispositivos móviles envíen notificaciones orientadas a los usuarios.

Consulta los documentos de Firebase Cloud Messaging para obtener más información.

Diferencias con las notificaciones push de Parse

Cada aplicación de Parse instalada en un dispositivo registrado para recibir notificaciones tiene un objeto Installation asociado, en el que se almacenan todos los datos necesarios para orientar las notificaciones. Installation es una subclase de PFUser, lo que significa que puedes agregar los datos adicionales que desees a tus instancias de Installation.

El compositor de Notifications proporciona segmentos de usuarios predefinidos según datos como la app, la versión de la app y el idioma del dispositivo. Puedes crear segmentos de usuarios más complejos con eventos y propiedades de Google Analytics para Firebase a fin de crear públicos. Consulta la guía de ayuda sobre públicos para obtener más información. Estos datos de orientación no se pueden ver en Firebase Realtime Database.

Estrategia de migración sugerida

Migra los tokens de dispositivo

Parse usa tokens de dispositivo del APNS para orientar las notificaciones a las instalaciones. En cambio, FCM usa tokens de registro de FCM que se asignan a los tokens de dispositivo del APNS. Solo debes agregar el SDK de FCM a tu app de iOS para que este obtenga un token de FCM automáticamente.

Migra los canales a temas de FCM

Si estás usando canales Parse para enviar notificaciones, puedes migrarlos a temas de FCM, que proporcionan el mismo modelo de editor y suscriptor. Para administrar la transición desde Parse a FCM, puedes escribir una versión nueva de la app que use el SDK de Parse para anular la suscripción desde los canales de Parse y el SDK de FCM para suscribirse a los temas de FCM correspondientes.

Por ejemplo, si tu usuario se suscribió al tema "Giants", debes hacer algo como lo siguiente:

PFInstallation *currentInstallation = [PFInstallation currentInstallation];
[currentInstallation removeObject:@"Giants" forKey:@"channels"];
[currentInstallation saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
  if (succedeed) {
    [[FIRMessaging messaging] subscribeToTopic:@"/topics/Giants"];
  } else {
    // Something went wrong unsubscribing
  }
}];

Con esta estrategia, puedes enviar mensajes al canal de Parse y al tema de FCM correspondiente, con lo cual se satisfacen las necesidades de los usuarios de ambas versiones, la antigua y la nueva. Una vez que haya migrado una cantidad suficiente de usuarios de la versión de la app compatible solo con Parse, puedes retirar esa versión y comenzar a enviar solo con FCM.

Consulta los documentos de temas de FCM para obtener más información.

Firebase Remote Config

Firebase Remote Config es un servicio en la nube que te permite cambiar el aspecto y el comportamiento de tu app sin pedirles a los usuarios que descarguen una actualización de la app. Cuando usas Remote Config, creas valores predeterminados en la app que controlan el comportamiento y el aspecto de tu app. Luego, puedes usar Firebase console para anular los valores predeterminados para todos los usuarios de la app o para segmentos de tu base de usuarios.

Firebase Remote Config puede ser muy útil durante tus migraciones en casos en los que quieras probar diferentes soluciones y poder cambiar más clientes a un proveedor diferente de forma dinámica. Por ejemplo, si tienes una versión de tu app que usa tanto Firebase como Parse para los datos, puedes usar una regla de percentil aleatorio para determinar qué clientes leen los datos desde Firebase y aumentar gradualmente el porcentaje.

Para obtener más información sobre Firebase Remote Config, consulta la Introducción a Remote Config.

Diferencias con la configuración de Parse

Con la configuración de Parse, puedes agregar pares clave-valor a tu app en el panel de configuración de Parse y luego puedes obtener la PFConfig en el cliente. Cada instancia de PFConfig que obtienes es invariable siempre. Cuando recuperes una nueva PFConfig en el futuro desde la red, no modificará ninguna instancia de PFConfig existente, sino que creará una nueva y hará que esté disponible a través de currentConfig.

Con Firebase Remote Config, creas valores predeterminados en la app para los pares clave-valor que pueden anularse desde Firebase console, y puedes usar reglas y condiciones para proporcionar variaciones de la experiencia del usuario de tu app en diferentes segmentos. Firebase Remote Config implementa una clase singleton que pone los pares clave-valor a disposición de tu app. Inicialmente, el singleton devuelve los valores predeterminados que defines en la app. Puedes obtener un conjunto nuevo de valores desde el servidor en cualquier momento conveniente para tu app. Después de obtener el conjunto nuevo de forma correcta, puedes elegir cuándo activarlo para poner los valores nuevos a disposición a la app.

Estrategia de migración sugerida

Para cambiarte a Firebase Remote Config, puedes copiar los pares clave-valor de tu configuración de Parse a Firebase console y luego implementar una versión nueva de la app que use Firebase Remote Config.

Si deseas experimentar con la configuración de Parse y con Firebase Remote Config, puedes implementar una versión nueva de la app que use ambos SDK hasta se migren suficientes usuarios desde la versión solo para Parse.

Comparación de código

Parse

[PFConfig getConfigInBackgroundWithBlock:^(PFConfig *config, NSError *error) {
  if (!error) {
    NSLog(@"Yay! Config was fetched from the server.");
  } else {
    NSLog(@"Failed to fetch. Using Cached Config.");
    config = [PFConfig currentConfig];
  }

  NSString *welcomeMessage = config[@"welcomeMessage"];
  if (!welcomeMessage) {
    NSLog(@"Falling back to default message.");
    welcomeMessage = @"Welcome!";
  }
}];

Firebase

FIRRemoteConfig remoteConfig = [FIRRemoteConfig remoteConfig];
// Set defaults from a plist file
[remoteConfig setDefaultsFromPlistFileName:@"RemoteConfigDefaults"];

[remoteConfig fetchWithCompletionHandler:^(FIRRemoteConfigFetchStatus status, NSError *error) {
  if (status == FIRRemoteConfigFetchStatusSuccess) {
    NSLog(@"Yay! Config was fetched from the server.");
    // Once the config is successfully fetched it must be activated before newly fetched
    // values are returned.
    [self.remoteConfig activateFetched];
  } else {
    NSLog(@"Failed to fetch. Using last fetched or default.");
  }
}];

// ...

// When this is called, the value of the latest fetched and activated config is returned;
// if there's none, the default value is returned.
NSString welcomeMessage = remoteConfig[@"welcomeMessage"].stringValue;