Migrer votre application iOS Parse vers Firebase

Si vous utilisez Parse et que vous recherchez une autre solution de backend en tant que service, Firebase peut être le choix idéal pour votre application iOS.

Ce guide explique comment intégrer des services spécifiques à votre application. Pour obtenir des instructions de configuration de base de Firebase, consultez le guide Configuration iOS+.

Google Analytics

Google Analytics est une solution de mesure des applications sans frais qui fournit des insights sur l'utilisation de l'application et l'engagement utilisateur. Analytics s'intègre aux fonctionnalités Firebase et vous fournit des rapports illimités sur jusqu'à 500 événements distincts que vous pouvez définir à l'aide du SDK Firebase.

Pour en savoir plus, consultez la documentation Google Analytics.

Stratégie de migration suggérée

L'utilisation de différents fournisseurs d'analyse est un scénario courant qui s'applique facilement à Google Analytics. Il vous suffit de l'ajouter à votre application pour profiter des événements et des propriétés utilisateur que Analytics collecte automatiquement, comme la première ouverture, la mise à jour de l'application, le modèle de l'appareil et l'âge.

Pour les événements personnalisés et les propriétés utilisateur, vous pouvez utiliser une stratégie d'écriture double à l'aide de Parse Analytics et de Google Analytics pour consigner les événements et les propriétés, ce qui vous permet de déployer progressivement la nouvelle solution.

Comparaison de code

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

// 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 est une base de données NoSQL hébergée dans le cloud. Les données sont stockées au format JSON et synchronisées en temps réel avec chaque client connecté.

Pour en savoir plus, consultez la documentation Firebase Realtime Database.

Différences avec les données d'analyse

Objets

Dans Parse, vous stockez un PFObject, ou une sous-classe de celui-ci, qui contient des paires clé-valeur de données compatibles avec le format JSON. Les données sont sans schéma, ce qui signifie que vous n'avez pas besoin de spécifier les clés existantes sur chaque PFObject.

Toutes les données Firebase Realtime Database sont stockées en tant qu'objets JSON, et il n'existe aucun équivalent pour PFObject. Il vous suffit d'écrire dans l'arbre JSON des valeurs de types qui correspondent aux types JSON disponibles.

Voici un exemple de sauvegarde des meilleurs scores d'un jeu.

Analyser
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
  }
}];
Pour en savoir plus, consultez le guide Lire et écrire des données sur les plates-formes Apple.

Relations entre les données

Un PFObject peut avoir une relation avec un autre PFObject: n'importe quel objet peut utiliser d'autres objets comme valeurs.

Dans Firebase Realtime Database, les relations sont mieux exprimées à l'aide de structures de données plates qui divisent les données en chemins distincts, afin qu'elles puissent être téléchargées efficacement dans des appels distincts.

Voici un exemple de structuration de la relation entre les posts d'une application de blog et leurs auteurs.

Analyser
// 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]

La mise en page des données suivante est obtenue.

{
  // 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"
    }
    ...
  }
}
Pour en savoir plus, consultez le guide Structurer votre base de données.

Lire les données

Dans Parse, vous lisez les données à l'aide de l'ID d'un objet Parse spécifique ou en exécutant des requêtes à l'aide de PFQuery.

Dans Firebase, vous récupérez les données en attachant un écouteur asynchrone à une référence de base de données. L'écouteur est déclenché une fois pour l'état initial des données et une fois lorsque les données changent. Vous n'avez donc pas besoin d'ajouter de code pour déterminer si les données ont changé.

Voici un exemple de récupération des scores d'un joueur particulier, basé sur l'exemple présenté dans la section "Objects" (Objets).

Analyser
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);
}];
Pour en savoir plus sur les types d'écouteurs d'événements disponibles et sur la manière de trier et de filtrer les données, consultez le guide Lire et écrire des données sur les plates-formes Apple.

Stratégie de migration suggérée

Redécouvrir vos données

Firebase Realtime Database est optimisé pour synchroniser les données en millisecondes sur tous les clients connectés. La structure de données qui en résulte est différente des données de base de Parse. Par conséquent, la première étape de votre migration consiste à déterminer les modifications requises pour vos données, y compris les suivantes:

  • Comment mapper vos objets Parse sur les données Firebase
  • Si vous avez des relations parent-enfant, comment diviser vos données en différents chemins afin qu'elles puissent être téléchargées efficacement dans des appels distincts.

Migrer vos données

Une fois que vous avez décidé de la structure de vos données dans Firebase, vous devez planifier la gestion de la période pendant laquelle votre application doit écrire dans les deux bases de données. Vous disposez des options suivantes :

Synchronisation en arrière-plan

Dans ce scénario, vous disposez de deux versions de l'application: l'ancienne version qui utilise Parse et la nouvelle version qui utilise Firebase. Les synchronisations entre les deux bases de données sont gérées par le code cloud Parse (Parse vers Firebase). Votre code écoute les modifications sur Firebase et les synchronise avec Parse. Avant de pouvoir utiliser la nouvelle version, vous devez:

  • Convertissez vos données Parse existantes en nouvelle structure Firebase, puis écrivez-les dans Firebase Realtime Database.
  • Écrivez des fonctions de code cloud Parse qui utilisent l'API REST Firebase pour écrire dans les modifications Firebase Realtime Database apportées aux données Parse par les anciens clients.
  • Écrivez et déployez du code qui écoute les modifications sur Firebase et les synchronise avec la base de données Parse.

Ce scénario garantit une séparation nette entre l'ancien et le nouveau code, et simplifie les clients. Les défis de ce scénario consistent à gérer de grands ensembles de données lors de l'exportation initiale et à s'assurer que la synchronisation bidirectionnelle ne génère pas de récursion infinie.

Double écriture

Dans ce scénario, vous écrivez une nouvelle version de l'application qui utilise à la fois Firebase et Parse, en utilisant le code Cloud Parse pour synchroniser les modifications apportées par les anciens clients à partir des données Parse avec le Firebase Realtime Database. Lorsque suffisamment de personnes auront migré depuis la version de l'application uniquement compatible avec Parse, vous pourrez supprimer le code Parse de la version à double écriture.

Ce scénario ne nécessite aucun code côté serveur. Ses inconvénients sont que les données auxquelles on n'accède pas ne sont pas migrées et que la taille de votre application augmente avec l'utilisation des deux SDK.

Firebase Authentication

Firebase Authentication peut authentifier les utilisateurs à l'aide de mots de passe et de fournisseurs d'identité fédérés populaires tels que Google, Facebook et Twitter. Il fournit également des bibliothèques d'UI pour vous éviter l'investissement important requis pour implémenter et gérer une expérience d'authentification complète pour votre application sur toutes les plates-formes.

Pour en savoir plus, consultez la documentation Firebase Authentication.

Différences avec l'authentification Parse

Parse fournit une classe d'utilisateur spécialisée appelée PFUser qui gère automatiquement les fonctionnalités requises pour la gestion des comptes utilisateur. PFUser est une sous-classe de PFObject, ce qui signifie que les données utilisateur sont disponibles dans les données d'analyse et peuvent être étendues avec des champs supplémentaires comme n'importe quel autre PFObject.

Un FIRUser dispose d'un ensemble fixe de propriétés de base (un ID unique, une adresse e-mail principale, un nom et une URL de photo) stockées dans la base de données d'utilisateurs d'un projet distinct. Ces propriétés peuvent être modifiées par l'utilisateur. Vous ne pouvez pas ajouter directement d'autres propriétés à l'objet FIRUser. À la place, vous pouvez stocker les propriétés supplémentaires dans votre Firebase Realtime Database.

Voici un exemple de procédure permettant d'enregistrer un utilisateur et d'ajouter un champ de numéro de téléphone supplémentaire.

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

Stratégie de migration suggérée

Migrer des comptes

Pour migrer des comptes utilisateur de Parse vers Firebase, exportez votre base de données utilisateur vers un fichier JSON ou CSV, puis importez le fichier dans votre projet Firebase à l'aide de la commande auth:import de la CLI Firebase.

Commencez par exporter votre base de données utilisateur depuis la console Parse ou votre base de données auto-hébergée. Par exemple, un fichier JSON exporté depuis la console Parse peut ressembler à ceci:

{ // 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",
  ...
}

Ensuite, transformez le fichier exporté au format requis par la CLI Firebase. Utilisez l'objectId de vos utilisateurs Parse comme localId de vos utilisateurs Firebase. En outre, encodez les valeurs bcryptPassword de Parse en Base64 et utilisez-les dans le champ passwordHash. Exemple :

{
  "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
        }
      ]
    }
  ]
}

Enfin, importez le fichier transformé avec la CLI Firebase, en spécifiant bcrypt comme algorithme de hachage:

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

Migrer les données utilisateur

Si vous stockez des données supplémentaires pour vos utilisateurs, vous pouvez les migrer vers Firebase Realtime Database à l'aide des stratégies décrites dans la section Migration de données. Si vous migrez des comptes à l'aide du flux décrit dans la section Migration des comptes, vos comptes Firebase auront les mêmes ID que vos comptes Parse, ce qui vous permettra de migrer et de reproduire facilement toutes les relations associées à l'ID utilisateur.

Firebase Cloud Messaging

Firebase Cloud Messaging (FCM) est une solution de messagerie multiplate-forme qui vous permet de transmettre des messages et des notifications de manière fiable et sans frais. Le compositeur de notifications est un service sans frais basé sur Firebase Cloud Messaging qui permet aux développeurs d'applications mobiles d'envoyer des notifications ciblées aux utilisateurs.

Pour en savoir plus, consultez la documentation Firebase Cloud Messaging .

Différences avec les notifications push Parse

Chaque application Parse installée sur un appareil enregistré pour les notifications est associée à un objet Installation, dans lequel vous stockez toutes les données nécessaires pour cibler les notifications. Installation est une sous-classe de PFUser, ce qui signifie que vous pouvez ajouter toutes les données supplémentaires que vous souhaitez à vos instances Installation.

L'outil de création de notifications fournit des segments d'utilisateurs prédéfinis en fonction d'informations telles que l'application, la version de l'application et la langue de l'appareil. Vous pouvez créer des segments d'utilisateurs plus complexes à l'aide d'événements et de propriétés Google Analytics pour créer des audiences. Pour en savoir plus, consultez le guide d'aide sur les audiences. Ces informations de ciblage ne sont pas visibles dans Firebase Realtime Database.

Stratégie de migration suggérée

Migrer des jetons d'appareil

Alors que Parse utilise des jetons d'appareil APN pour cibler les installations de notifications, FCM utilise des jetons d'enregistrement FCM mappés sur les jetons d'appareil APN. Il vous suffit d'ajouter le SDK FCM à votre application Apple pour qu'il récupère automatiquement un jeton FCM.

Migrer des chaînes vers des sujets FCM

Si vous utilisez des canaux Parse pour envoyer des notifications, vous pouvez migrer vers des thèmes FCM, qui fournissent le même modèle éditeur-abonné. Pour gérer la transition de Parse vers FCM, vous pouvez écrire une nouvelle version de l'application qui utilise le SDK Parse pour se désabonner des canaux Parse et le SDK FCM pour s'abonner aux sujets FCM correspondants.

Par exemple, si votre utilisateur est abonné au sujet "Giants", vous pouvez procéder comme suit:

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

Grâce à cette stratégie, vous pouvez envoyer des messages à la fois au canal Parse et au sujet FCM correspondant, ce qui permet de prendre en charge les utilisateurs des anciennes et nouvelles versions. Lorsque suffisamment d'utilisateurs auront migré depuis la version de l'application réservée à Parse, vous pourrez abandonner cette version et commencer à envoyer des messages à l'aide de FCM uniquement.

Pour en savoir plus, consultez la documentation sur les sujets FCM.

Firebase Remote Config

Firebase Remote Config est un service cloud qui vous permet de modifier le comportement et l'apparence de votre application sans demander aux utilisateurs de télécharger une mise à jour. Lorsque vous utilisez Remote Config, vous créez des valeurs par défaut dans l'application qui contrôlent son comportement et son apparence. Vous pouvez ensuite utiliser la console Firebase pour remplacer ces valeurs par défaut pour tous les utilisateurs de l'application ou pour certains segments de la base d'utilisateurs.

Firebase Remote Config peut être très utile lors de vos migrations si vous souhaitez tester différentes solutions et pouvoir transférer de manière dynamique davantage de clients vers un autre fournisseur. Par exemple, si une version de votre application utilise à la fois Firebase et Parse pour les données, vous pouvez utiliser une règle de percentile aléatoire pour déterminer quels clients lisent à partir de Firebase et augmenter progressivement le pourcentage.

Pour en savoir plus sur Firebase Remote Config, consultez la présentation de Remote Config.

Différences avec la configuration Parse

Avec la configuration Parse, vous pouvez ajouter des paires clé-valeur à votre application dans le tableau de bord de configuration Parse, puis extraire le PFConfig sur le client. Chaque instance PFConfig que vous obtenez est toujours immuable. Lorsque vous récupérerez une nouvelle PFConfig à partir du réseau à l'avenir, elle ne modifiera aucune instance PFConfig existante, mais en créera une et la rendra disponible via currentConfig.

Avec Firebase Remote Config, vous créez des valeurs par défaut dans l'application pour les paires clé-valeur que vous pouvez remplacer depuis la console Firebase. Vous pouvez également utiliser des règles et des conditions pour proposer des variations de l'expérience utilisateur de votre application à différents segments de votre base d'utilisateurs. Firebase Remote Config implémente une classe singleton qui met les paires clé/valeur à la disposition de votre application. Au départ, le singleton renvoie les valeurs par défaut que vous définissez dans l'application. Vous pouvez extraire un nouvel ensemble de valeurs du serveur à tout moment qui convient à votre application. Une fois le nouvel ensemble extrait, vous pouvez choisir de l'activer pour mettre les nouvelles valeurs à la disposition de l'application.

Stratégie de migration suggérée

Pour passer à Firebase Remote Config, copiez les paires clé/valeur de votre configuration Parse dans la console Firebase, puis déployez une nouvelle version de l'application qui utilise Firebase Remote Config.

Si vous souhaitez tester à la fois Parse Config et Firebase Remote Config, vous pouvez déployer une nouvelle version de l'application qui utilise les deux SDK jusqu'à ce qu'un nombre suffisant d'utilisateurs aient migré depuis la version Parse uniquement.

Comparaison de code

Analyser

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